Skip to content

Commit

Permalink
Add GDScript lexer (#1036)
Browse files Browse the repository at this point in the history
This commit adds a lexer for GDScript.
  • Loading branch information
leonidboykov authored and pyrmont committed Jul 18, 2019
1 parent e3598c6 commit b6411b1
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 0 deletions.
18 changes: 18 additions & 0 deletions lib/rouge/demos/gdscript
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
extends Node

# Variables & Built-in Types

var a = 5
var b = true
var s = "Hello"
var arr = [1, 2, 3]

# Constants & Enums

const ANSWER = 42
enum { UNIT_NEUTRAL, UNIT_ENEMY, UNIT_ALLY }

# Functions

func _ready():
print("Hello, World")
171 changes: 171 additions & 0 deletions lib/rouge/lexers/gdscript.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

module Rouge
module Lexers
class GDScript < RegexLexer
title "GDScript"
desc "The Godot Engine programming language (https://godotengine.org/)"
tag 'gdscript'
aliases 'gd', 'gdscript'
filenames '*.gd'
mimetypes 'text/x-gdscript', 'application/x-gdscript'

def self.keywords
@keywords = %w(
and in not or as breakpoint class class_name extends is func setget
signal tool const enum export onready static var break continue
if elif else for pass return match while remote master puppet
remotesync mastersync puppetsync
).join('|')
end

# Reserved for future implementation
def self.keywords_reserved
@keywords_reserved = %w(
do switch case
).join('|')
end

def self.builtins
builtins = %w(
Color8 ColorN abs acos asin assert atan atan2 bytes2var ceil char
clamp convert cos cosh db2linear decimals dectime deg2rad dict2inst
ease exp floor fmod fposmod funcref hash inst2dict instance_from_id
is_inf is_nan lerp linear2db load log max min nearest_po2 pow
preload print print_stack printerr printraw prints printt rad2deg
rand_range rand_seed randf randi randomize range round seed sign
sin sinh sqrt stepify str str2var tan tan tanh type_exist typeof
var2bytes var2str weakref yield
).join('|')
end

def self.builtins_type
@builtins_type = %w(
bool int float String Vector2 Rect2 Transform2D Vector3 AABB
Plane Quat Basis Transform Color RID Object NodePath Dictionary
Array PoolByteArray PoolIntArray PoolRealArray PoolStringArray
PoolVector2Array PoolVector3Array PoolColorArray null
).join('|')
end

state :root do
rule %r/\n/, Text
rule %r/[^\S\n]+/, Text
rule %r/#.*/, Comment::Single
rule %r/[\[\]{}:(),;]/, Punctuation
rule %r/\\\n/, Text
rule %r/(in|and|or|not)\b/, Operator::Word
rule %r/!=|==|<<|>>|&&|\+=|-=|\*=|\/=|%=|&=|\|=|\|\||[-~+\/*%=<>&^.!|$]/, Operator
rule %r/(func)((?:\s|\\)+)/ do
groups Keyword, Text
push :funcname
end
rule %r/(class)((?:\s|\\)+)/ do
groups Keyword, Text
push :classname
end
mixin :keywords
mixin :builtins
rule %r/"""/, Str::Double, :escape_tdqs
rule %r/'''/, Str::Double, :escape_tsqs
rule %r/"/, Str::Double, :escape_dqs
rule %r/'/, Str::Double, :escape_sqs
mixin :name
mixin :numbers
end

state :keywords do
rule %r/\b(#{GDScript.keywords})\b/, Keyword
rule %r/\b(#{GDScript.keywords_reserved})\b/, Keyword::Reserved
end

state :builtins do
rule %r/\b(#{GDScript.builtins})\b/, Name::Builtin
rule %r/\b((self|false|true)|(PI|TAU|NAN|INF))\b/, Name::Builtin::Pseudo
rule %r/\b(#{GDScript.builtins_type})\b/, Keyword::Type
end

state :numbers do
rule %r/(\d+\.\d*|\d*\.\d+)([eE][+-]?[0-9]+)?j?/, Num::Float
rule %r/\d+[eE][+-]?[0-9]+j?/, Num::Float
rule %r/0[xX][a-fA-F0-9]+/, Num::Hex
rule %r/\d+j?/, Num::Integer
end

state :name do
rule %r/[a-zA-Z_]\w*/, Name
end

state :funcname do
rule %r/[a-zA-Z_]\w*/, Name::Function, :pop!
end

state :classname do
rule %r/[a-zA-Z_]\w*/, Name::Class, :pop!
end

state :string_escape do
rule %r/\\([\\abfnrtv"\']|\n|N\{.*?\}|u[a-fA-F0-9]{4}|U[a-fA-F0-9]{8}|x[a-fA-F0-9]{2}|[0-7]{1,3})/, Str::Escape
end

state :strings_single do
rule %r/%(\(\w+\))?[-#0 +]*([0-9]+|[*])?(\.([0-9]+|[*]))?[hlL]?[E-GXc-giorsux%]/, Str::Interpol
rule %r/[^\\'%\n]+/, Str::Single
rule %r/["\\]/, Str::Single
rule %r/%/, Str::Single
end

state :strings_double do
rule %r/%(\(\w+\))?[-#0 +]*([0-9]+|[*])?(\.([0-9]+|[*]))?[hlL]?[E-GXc-giorsux%]/, Str::Interpol
rule %r/[^\\"%\n]+/, Str::Double
rule %r/['\\]/, Str::Double
rule %r/%/, Str::Double
end

state :dqs do
rule %r/"/, Str::Double, :pop!
rule %r/\\\\|\\"|\\\n/, Str::Escape
mixin :strings_double
end

state :escape_dqs do
mixin :string_escape
mixin :dqs
end

state :sqs do
rule %r/'/, Str::Single, :pop!
rule %r/\\\\|\\'|\\\n/, Str::Escape
mixin :strings_single
end

state :escape_sqs do
mixin :string_escape
mixin :sqs
end

state :tdqs do
rule %r/"""/, Str::Double, :pop!
mixin :strings_double
rule %r/\n/, Str::Double
end

state :escape_tdqs do
mixin :string_escape
mixin :tdqs
end

state :tsqs do
rule %r/'''/, Str::Single, :pop!
mixin :strings_single
rule %r/\n/, Str::Single
end

state :escape_tsqs do
mixin :string_escape
mixin :tsqs
end
end
end
end
19 changes: 19 additions & 0 deletions spec/lexers/gdscript_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*- #
# frozen_string_literal: true

describe Rouge::Lexers::GDScript do
let(:subject) { Rouge::Lexers::GDScript.new }

describe 'guessing' do
include Support::Guessing

it 'guesses by filename' do
assert_guess :filename => 'foo.gd'
end

it 'guesses by mimetype' do
assert_guess :mimetype => 'text/x-gdscript'
assert_guess :mimetype => 'application/x-gdscript'
end
end
end
44 changes: 44 additions & 0 deletions spec/visual/samples/gdscript
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
""" Everything on these lines is considered
a comment. """

tool
extends BaseClass
class_name ScriptName, "res://path/to/optional/icon.svg"

# Variables & Built-in Types

var i = 5
var f = 1.23
var h = 0xDEADC0DE
var b = true
var s = "Hello"
var arr = [1, 2, 3]
var dict = {"key": "value", 2: 3}
var v = Vector2(1, 2)
var t = "Hello, " \
+ "very long " \
+ "text"

# Constants & Enums

const ANSWER = 42
enum { UNIT_NEUTRAL, UNIT_ENEMY, UNIT_ALLY }

# Functions

signal some_signal

func some_function(param1: int, param2: float) -> int:
var local_var = 5
var format_string_1 = "We're waiting for %s." % "Godot"
var format_string_2 = "We're waiting for {str}"
var actual_string = format_string_2.format({"str": "Godot"})
print(param1, param2, local_var)
return local_var

# Inner Classes
class InnerClass extends BaseClass:
var local_var

func _init():
print(local_var)

0 comments on commit b6411b1

Please sign in to comment.