This repository has been archived on 2024-02-25. You can view files and clone it, but cannot push or open issues or pull requests.
Wol/addons/Wol/core/compiler/parser.gd

953 lines
27 KiB
GDScript

extends Object
const Constants = preload('res://addons/Wol/core/constants.gd')
const Lexer = preload('res://addons/Wol/core/compiler/lexer.gd')
var _tokens = []
var title = ''
func _init(tokens, title):
self._tokens = tokens
self.title = title
enum Associativity {
Left,
Right,
None
}
func parse_node():
return WolNode.new('Start', null, self)
func next_symbol_is(valid_types):
var type = self._tokens.front().type
for valid_type in valid_types:
if type == valid_type:
return true
return false
# NOTE:0 look ahead for `<<` and `else`
func next_symbols_are(valid_types):
var temporary = [] + _tokens
for type in valid_types:
if temporary.pop_front().type != type:
return false
return true
func expect_symbol(token_types = []):
var token = _tokens.pop_front() as Lexer.Token
if token_types.size() == 0:
if token.type == Constants.TokenType.EndOfInput:
assert(false, 'Unexpected end of input')
return null
return token
for type in token_types:
if token.type == type:
return token
var token_names = []
for type in token_types:
token_names.append(Constants.token_type_name(type))
var error_guess = '\n'
if Constants.token_type_name(token.type) == 'Identifier' \
and Constants.token_type_name(token_types[0]) == 'OptionEnd':
error_guess += 'Does the node your refer to have a space in it?'
else:
error_guess = ''
var error_data = [
token.filename,
title,
token.line_number,
token.column,
PoolStringArray(token_names).join(', '),
Constants.token_type_name(token.type),
error_guess
]
assert(false, '[%s|%s:%d:%d]:\nExpected token "%s" but got "%s"%s' % error_data)
static func tab(indent_level, input, newline = true):
return '%*s| %s%s' % [indent_level * 2, '', input, '' if not newline else '\n']
func tokens():
return _tokens
class ParseNode:
var name = ''
var parent
var line_number = -1
var tags = []
func _init(parent, parser):
self.parent = parent
var tokens = parser.tokens() as Array
if tokens.size() > 0:
line_number = tokens.front().line_number
else:
line_number = -1
tags = []
func tree_string(indent_level):
return 'Not_implemented'
func tags_to_string(indent_level):
return '%s' % 'TAGS<tags_to_string>NOTIMPLEMENTED'
func get_node_parent():
var node = self
while node != null:
if node is ParseNode:
return node as WolNode
node = node.parent
return null
func tab(indent_level, input, newline = true):
return '%*s| %s%s' % [ indent_level * 2, '', input, '' if !newline else '\n']
func set_parent(parent):
self.parent = parent
#this is a Wol Node - contains all the text
class WolNode extends ParseNode:
var source = ''
var editor_node_tags = []
var statements = []
func _init(name, parent, parser).(parent, parser):
self.name = name
while parser.tokens().size() > 0 \
and not parser.next_symbol_is([Constants.TokenType.Dedent, Constants.TokenType.EndOfInput]):
statements.append(Statement.new(self, parser))
func tree_string(indent_level):
var info = []
for statement in statements:
info.append(statement.tree_string(indent_level + 1))
return PoolStringArray(info).join('')
# TODO: Evaluate use
class Header extends ParseNode:
pass
class Statement extends ParseNode:
var Type = Constants.StatementTypes
var type = -1
var block
var if_statement
var option_statement
var assignment
var shortcut_option_group
var custom_command
var line = ''
func _init(parent, parser).(parent, parser):
if Block.can_parse(parser):
block = Block.new(self, parser)
type = Type.Block
elif IfStatement.can_parse(parser):
if_statement = IfStatement.new(self, parser)
type = Type.IfStatement
elif OptionStatement.can_parse(parser):
option_statement = OptionStatement.new(self, parser)
type = Type.OptionStatement
elif Assignment.can_parse(parser):
assignment = Assignment.new(self, parser)
type = Type.AssignmentStatement
elif ShortcutOptionGroup.can_parse(parser):
shortcut_option_group = ShortcutOptionGroup.new(self, parser)
type = Type.ShortcutOptionGroup
elif CustomCommand.can_parse(parser):
custom_command = CustomCommand.new(self, parser)
type = Type.CustomCommand
elif parser.next_symbol_is([Constants.TokenType.Text]):
line = parser.expect_symbol([Constants.TokenType.Text]).value
type = Type.Line
else:
printerr('Expected a statement but got %s instead. (probably an imbalanced if statement)' % parser.tokens().front()._to_string())
var tags = []
while parser.next_symbol_is([Constants.TokenType.TagMarker]):
parser.expect_symbol([Constants.TokenType.TagMarker])
var tag = parser.expect_symbol([Constants.TokenType.Identifier]).value
tags.append(tag)
if tags.size() > 0:
self.tags = tags
func tree_string(indent_level):
var info = []
match type:
Type.Block:
info.append(block.tree_string(indent_level))
Type.IfStatement:
info.append(if_statement.tree_string(indent_level))
Type.AssignmentStatement:
info.append(assignment.tree_string(indent_level))
Type.OptionStatement:
info.append(option_statement.tree_string(indent_level))
Type.ShortcutOptionGroup:
info.append(shortcut_option_group.tree_string(indent_level))
Type.CustomCommand:
info.append(custom_command.tree_string(indent_level))
Type.Line:
info.append(tab(indent_level, 'Line: %s' % line))
_:
printerr('Cannot print statement')
return PoolStringArray(info).join('')
class CustomCommand extends ParseNode:
enum Type {
Expression,
ClientCommand
}
var type = -1
var expression
var client_command
func _init(parent, parser).(parent, parser):
parser.expect_symbol([Constants.TokenType.BeginCommand])
var command_tokens = []
command_tokens.append(parser.expect_symbol())
while not parser.next_symbol_is([Constants.TokenType.EndCommand]):
command_tokens.append(parser.expect_symbol())
parser.expect_symbol([Constants.TokenType.EndCommand])
#if first token is identifier and second is leftt parenthesis
#evaluate as function
if (command_tokens.size() > 1 && command_tokens[0].type == Constants.TokenType.Identifier
&& command_tokens[1].type == Constants.TokenType.LeftParen):
var p = get_script().new(command_tokens, parser.library)
var expression = ExpressionNode.parse(self, p)
type = Type.Expression
self.expression = expression
else:
#otherwise evaluuate command
type = Type.ClientCommand
self.client_command = command_tokens[0].value
func tree_string(indent_level):
match type:
Type.Expression:
return tab(indent_level,'Expression: %s'% expression.tree_string(indent_level+1))
Type.ClientCommand:
return tab(indent_level,'Command: %s' % client_command)
return ''
static func can_parse(parser):
return (parser.next_symbols_are([Constants.TokenType.BeginCommand, Constants.TokenType.Text])
or parser.next_symbols_are([Constants.TokenType.BeginCommand, Constants.TokenType.Identifier]))
class ShortcutOptionGroup extends ParseNode:
var options = []
func _init(parent, parser).(parent, parser):
# parse options until there is no more
# expect one otherwise invalid
var index = 1
options.append(ShortCutOption.new(index, self, parser))
index += 1
while parser.next_symbol_is([Constants.TokenType.ShortcutOption]):
options.append(ShortCutOption.new(index, self, parser))
index += 1
func tree_string(indent_level):
var info = []
info.append(tab(indent_level,'Shortcut Option Group{'))
for option in options:
info.append(option.tree_string(indent_level+1))
info.append(tab(indent_level,'}'))
return PoolStringArray(info).join('')
static func can_parse(parser):
return parser.next_symbol_is([Constants.TokenType.ShortcutOption])
class ShortCutOption extends ParseNode:
var label = ''
var condition
var node
func _init(index, parent, parser).(parent, parser):
parser.expect_symbol([Constants.TokenType.ShortcutOption])
label = parser.expect_symbol([Constants.TokenType.Text]).value
# NOTE: Parse the conditional << if $x >> when it exists
var tags = []
while parser.next_symbols_are([Constants.TokenType.BeginCommand, Constants.TokenType.IfToken]) \
or parser.next_symbol_is([Constants.TokenType.TagMarker]):
if parser.next_symbols_are([Constants.TokenType.BeginCommand, Constants.TokenType.IfToken]):
parser.expect_symbol([Constants.TokenType.BeginCommand])
parser.expect_symbol([Constants.TokenType.IfToken])
condition = ExpressionNode.parse(self, parser)
parser.expect_symbol([Constants.TokenType.EndCommand])
elif parser.next_symbol_is([Constants.TokenType.TagMarker]):
parser.expect_symbol([Constants.TokenType.TagMarker])
var tag = parser.expect_symbol([Constants.TokenType.Identifier]).value
tags.append(tag)
self.tags = tags
# parse remaining statements
if parser.next_symbol_is([Constants.TokenType.Indent]):
parser.expect_symbol([Constants.TokenType.Indent])
node = WolNode.new('%s.%s' % [parent.name, index], self, parser)
parser.expect_symbol([Constants.TokenType.Dedent])
func tree_string(indent_level):
var info = []
info.append(tab(indent_level, 'Option \'%s\'' % label))
if condition != null:
info.append(tab(indent_level + 1, '(when:'))
info.append(condition.tree_string(indent_level + 2))
info.append(tab(indent_level + 1, '),'))
if node != null:
info.append(tab(indent_level, '{'))
info.append(node.tree_string(indent_level + 1))
info.append(tab(indent_level, '}'))
return PoolStringArray(info).join('')
#Blocks are groups of statements with the same indent level
class Block extends ParseNode:
var statements = []
func _init(parent, parser).(parent, parser):
#read indent
parser.expect_symbol([Constants.TokenType.Indent])
#keep reading statements until we hit a dedent
while not parser.next_symbol_is([Constants.TokenType.Dedent]):
#parse all statements including nested blocks
statements.append(Statement.new(self, parser))
#clean up dedent
parser.expect_symbol([Constants.TokenType.Dedent])
func tree_string(indent_level):
var info = []
info.append(tab(indent_level, 'Block {'))
for statement in statements:
info.append(statement.tree_string(indent_level + 1))
info.append(tab(indent_level, '}'))
return PoolStringArray(info).join('')
static func can_parse(parser):
return parser.next_symbol_is([Constants.TokenType.Indent])
# NOTE: Option Statements are links to other nodes
class OptionStatement extends ParseNode:
var destination = ''
var label = ''
func _init(parent, parser).(parent, parser):
var strings = []
# NOTE: parse [[LABEL
parser.expect_symbol([Constants.TokenType.OptionStart])
strings.append(parser.expect_symbol([Constants.TokenType.Text]).value)
# NOTE: if there is a | get the next string
if parser.next_symbol_is([Constants.TokenType.OptionDelimit]):
parser.expect_symbol([Constants.TokenType.OptionDelimit])
var t = parser.expect_symbol([Constants.TokenType.Text, Constants.TokenType.Identifier])
strings.append(t.value as String)
label = strings[0] if strings.size() > 1 else ''
destination = strings[1] if strings.size() > 1 else strings[0]
parser.expect_symbol([Constants.TokenType.OptionEnd])
func tree_string(indent_level):
if label != null:
return tab(indent_level,'Option: %s -> %s' % [label, destination])
else:
return tab(indent_level, 'Option: -> %s' % destination)
static func can_parse(parser):
return parser.next_symbol_is([Constants.TokenType.OptionStart])
class IfStatement extends ParseNode:
var clauses = []#
func _init(parent, parser).(parent, parser):
#<<if Expression>>
var prime = Clause.new()
parser.expect_symbol([Constants.TokenType.BeginCommand])
parser.expect_symbol([Constants.TokenType.IfToken])
prime.expression = ExpressionNode.parse(self, parser)
parser.expect_symbol([Constants.TokenType.EndCommand])
#read statements until 'endif' or 'else' or 'else if'
var statements = []#statement
while not parser.next_symbols_are([Constants.TokenType.BeginCommand, Constants.TokenType.EndIf]) \
and not parser.next_symbols_are([Constants.TokenType.BeginCommand, Constants.TokenType.ElseToken]) \
and not parser.next_symbols_are([Constants.TokenType.BeginCommand, Constants.TokenType.ElseIf]):
statements.append(Statement.new(self, parser))
#ignore dedent
while parser.next_symbol_is([Constants.TokenType.Dedent]):
parser.expect_symbol([Constants.TokenType.Dedent])
prime.statements = statements
clauses.append(prime)
#handle all else if
while parser.next_symbols_are([Constants.TokenType.BeginCommand, Constants.TokenType.ElseIf]):
var clause_elif = Clause.new()
#parse condition syntax
parser.expect_symbol([Constants.TokenType.BeginCommand])
parser.expect_symbol([Constants.TokenType.ElseIf])
clause_elif.expression = ExpressionNode.parse(self, parser)
parser.expect_symbol([Constants.TokenType.EndCommand])
var elif_statements = []#statement
while not parser.next_symbols_are([Constants.TokenType.BeginCommand, Constants.TokenType.EndIf]) \
and not parser.next_symbols_are([Constants.TokenType.BeginCommand, Constants.TokenType.ElseToken]) \
and not parser.next_symbols_are([Constants.TokenType.BeginCommand, Constants.TokenType.ElseIf]):
elif_statements.append(Statement.new(self, parser))
#ignore dedent
while parser.next_symbol_is([Constants.TokenType.Dedent]):
parser.expect_symbol([Constants.TokenType.Dedent])
clause_elif.statements = statements
clauses.append(clause_elif)
#handle else if exists
if (parser.next_symbols_are([Constants.TokenType.BeginCommand,
Constants.TokenType.ElseToken, Constants.TokenType.EndCommand])):
#expect no expression - just <<else>>
parser.expect_symbol([Constants.TokenType.BeginCommand])
parser.expect_symbol([Constants.TokenType.ElseToken])
parser.expect_symbol([Constants.TokenType.EndCommand])
#parse until hit endif
var clause_else = Clause.new()
var el_statements = []#statement
while not parser.next_symbols_are([Constants.TokenType.BeginCommand, Constants.TokenType.EndIf]):
el_statements.append(Statement.new(self, parser))
clause_else.statements = el_statements
clauses.append(clause_else)
#ignore dedent
while parser.next_symbol_is([Constants.TokenType.Dedent]):
parser.expect_symbol([Constants.TokenType.Dedent])
#finish
parser.expect_symbol([Constants.TokenType.BeginCommand])
parser.expect_symbol([Constants.TokenType.EndIf])
parser.expect_symbol([Constants.TokenType.EndCommand])
func tree_string(indent_level):
var info = []
var first = true
for clause in clauses:
if first:
info.append(tab(indent_level,'if:'))
elif clause.expression!=null:
info.append(tab(indent_level,'Else If'))
else:
info.append(tab(indent_level,'Else:'))
info.append(clause.tree_string(indent_level +1))
return info.join('')
static func can_parse(parser):
return parser.next_symbols_are([Constants.TokenType.BeginCommand, Constants.TokenType.IfToken])
pass
class ValueNode extends ParseNode:
const Value = preload('res://addons/Wol/core/value.gd')
const Lexer = preload('res://addons/Wol/core/compiler/lexer.gd')
var value
func _init(parent, parser, token = null).(parent, parser):
var t = token
if t == null :
parser.expect_symbol([Constants.TokenType.Number,
Constants.TokenType.Variable, Constants.TokenType.Str])
use_token(t)
#store value depending on type
func use_token(token):
match token.type:
Constants.TokenType.Number:
value = Value.new(float(token.value))
Constants.TokenType.Str:
value = Value.new(token.value)
Constants.TokenType.FalseToken:
value = Value.new(false)
Constants.TokenType.TrueToken:
value = Value.new(true)
Constants.TokenType.Variable:
value = Value.new(null)
value.type = Constants.ValueType.Variable
value.variable = token.value
Constants.TokenType.NullToken:
value = Value.new(null)
_:
printerr('%s, Invalid token type' % token.name)
func tree_string(indent_level):
return tab(indent_level, '%s' % value.value())
#Expressions encompass a wide range of things like:
# math (1 + 2 - 5 * 3 / 10 % 2)
# Identifiers
# Values
class ExpressionNode extends ParseNode:
var type
var value
var function
var params = []#ExpressionNode
func _init(parent, parser, value, function = '', params = []).(parent, parser):
#no function - means value
if value != null:
self.type = Constants.ExpressionType.Value
self.value = value
else:#function
self.type = Constants.ExpressionType.FunctionCall
self.function = function
self.params = params
func tree_string(indent_level):
var info = []
match type:
Constants.ExpressionType.Value:
return value.tree_string(indent_level)
Constants.ExpressionType.FunctionCall:
info.append(tab(indent_level,'Func[%s - params(%s)]:{'%[function, params.size()]))
for param in params:
info.append(param.tree_string(indent_level+1))
info.append(tab(indent_level,'}'))
return info.join('')
#using Djikstra's shunting-yard algorithm to convert
#stream of expresions into postfix notaion, then
#build a tree of expressions
static func parse(parent, parser):
var rpn = []
var op_stack = []
#track params
var func_stack = []
var valid_types = [
Constants.TokenType.Number,
Constants.TokenType.Variable,
Constants.TokenType.Str,
Constants.TokenType.LeftParen,
Constants.TokenType.RightParen,
Constants.TokenType.Identifier,
Constants.TokenType.Comma,
Constants.TokenType.TrueToken,
Constants.TokenType.FalseToken,
Constants.TokenType.NullToken
]
valid_types += Operator.op_types()
valid_types.invert()
var last
#read expression content
while parser.tokens().size() > 0 && parser.next_symbol_is(valid_types):
var next = parser.expect_symbol(valid_types)
if next.type == Constants.TokenType.Variable \
or next.type == Constants.TokenType.Number \
or next.type == Constants.TokenType.Str \
or next.type == Constants.TokenType.FalseToken \
or next.type == Constants.TokenType.TrueToken \
or next.type == Constants.TokenType.NullToken:
#output primitives
rpn.append(next)
elif next.type == Constants.TokenType.Identifier:
op_stack.push_back(next)
func_stack.push_back(next)
#next token is parent - left
next = parser.expect_symbol([Constants.TokenType.LeftParen])
op_stack.push_back(next)
elif next.type == Constants.TokenType.Comma:
#resolve sub expression before moving on
while op_stack.back().type != Constants.TokenType.LeftParen:
var p = op_stack.pop_back()
if p == null:
printerr('unbalanced parenthesis %s ' % next.name)
break
rpn.append(p)
#next token in op_stack left paren
# next parser token not allowed to be right paren or comma
if parser.next_symbol_is([Constants.TokenType.RightParen,
Constants.TokenType.Comma]):
printerr('Expected Expression : %s' % parser.tokens().front().name)
#find the closest function on stack
#increment parameters
func_stack.back().param_count+=1
elif Operator.is_op(next.type):
#this is an operator
#if this is a minus, we need to determine if it is a
#unary minus or a binary minus.
#unary minus looks like this : -1
#binary minus looks like this 2 - 3
#thins get complex when we say stuff like: 1 + -1
#but its easier when we realize that a minus
#is only unary when the last token was a left paren,
#an operator, or its the first token.
if next.type == Constants.TokenType.Minus:
if last == null \
or last.type == Constants.TokenType.LeftParen \
or Operator.is_op(last.type):
#unary minus
next.type = Constants.TokenType.UnaryMinus
#cannot assign inside expression
# x = a is the same as x == a
if next.type == Constants.TokenType.EqualToOrAssign:
next.type = Constants.TokenType.EqualTo
#operator precedence
while (ExpressionNode.is_apply_precedence(next.type, op_stack)):
var op = op_stack.pop_back()
rpn.append(op)
op_stack.push_back(next)
elif next.type == Constants.TokenType.LeftParen:
#entered parenthesis sub expression
op_stack.push_back(next)
elif next.type == Constants.TokenType.RightParen:
#leaving sub expression
# resolve order of operations
while op_stack.back().type != Constants.TokenType.LeftParen:
rpn.append(op_stack.pop_back())
if op_stack.back() == null:
printerr('Unbalanced parenthasis #RightParen. Parser.ExpressionNode')
op_stack.pop_back()
if op_stack.back().type == Constants.TokenType.Identifier:
#function call
#last token == left paren this == no params
#else
#we have more than 1 param
if last.type != Constants.TokenType.LeftParen:
func_stack.back().param_count+=1
rpn.append(op_stack.pop_back())
func_stack.pop_back()
#record last token used
last = next
#no more tokens : pop operators to output
while op_stack.size() > 0:
rpn.append(op_stack.pop_back())
#if rpn is empty then this is not expression
if rpn.size() == 0:
printerr('Error parsing expression: Expression not found!')
#build expression tree
var first = rpn.front()
var eval_stack = []#ExpressionNode
while rpn.size() > 0:
var next = rpn.pop_front()
if Operator.is_op(next.type):
#operation
var info = Operator.op_info(next.type)
if eval_stack.size() < info.arguments:
printerr('Error parsing : Not enough arguments for %s [ got %s expected - was %s]'%[Constants.token_type_name(next.type), eval_stack.size(), info.arguments])
var params = []#ExpressionNode
for i in range(info.arguments):
params.append(eval_stack.pop_back())
params.invert()
var function = get_func_name(next.type)
var expression = ExpressionNode.new(parent, parser, null, function, params)
eval_stack.append(expression)
elif next.type == Constants.TokenType.Identifier:
#function call
var function = next.value
var params = []#ExpressionNode
for i in range(next.param_count):
params.append(eval_stack.pop_back())
params.invert()
var expression = ExpressionNode.new(parent, parser, null, function, params)
eval_stack.append(expression)
else: #raw value
var value = ValueNode.new(parent, parser, next)
var expression = ExpressionNode.new(parent, parser, value)
eval_stack.append(expression)
#we should have a single root expression left
#if more then we failed ---- NANI
if eval_stack.size() != 1:
printerr('[%s] Error parsing expression (stack did not reduce correctly )' % first.name)
return eval_stack.pop_back()
static func get_func_name(type):
var string = ''
for key in Constants.TokenType.keys():
if Constants.TokenType[key] == type:
return key
return string
static func is_apply_precedence(type, operator_stack):
if operator_stack.size() == 0:
return false
if not Operator.is_op(type):
printerr('Unable to parse expression!')
var second = operator_stack.back().type
if not Operator.is_op(second):
return false
var first_info = Operator.op_info(type)
var second_info = Operator.op_info(second)
if (first_info.associativity == Associativity.Left &&
first_info.precedence <= second_info.precedence):
return true
if (first_info.associativity == Associativity.Right &&
first_info.precedence < second_info.precedence):
return true
return false
class Assignment extends ParseNode:
var destination
var value
var operation
func _init(parent, parser).(parent, parser):
parser.expect_symbol([Constants.TokenType.BeginCommand])
parser.expect_symbol([Constants.TokenType.Set])
destination = parser.expect_symbol([Constants.TokenType.Variable]).value
operation = parser.expect_symbol(Assignment.valid_ops()).type
value = ExpressionNode.parse(self, parser)
parser.expect_symbol([Constants.TokenType.EndCommand])
func tree_string(indent_level):
var info = []
info.append(tab(indent_level,'set:'))
info.append(tab(indent_level+1, destination))
info.append(tab(indent_level+1, Constants.token_type_name(operation)))
info.append(value.tree_string(indent_level+1))
return info.join('')
static func can_parse(parser):
return parser.next_symbols_are([
Constants.TokenType.BeginCommand,
Constants.TokenType.Set
])
static func valid_ops():
return [
Constants.TokenType.EqualToOrAssign,
Constants.TokenType.AddAssign,
Constants.TokenType.MinusAssign,
Constants.TokenType.DivideAssign,
Constants.TokenType.MultiplyAssign
]
class Operator extends ParseNode:
var op_type
func _init(parent, parser, op_type=null).(parent, parser):
if op_type == null :
self.op_type = parser.expect_symbol(Operator.op_types()).type
else:
self.op_type = op_type
func tree_string(indent_level):
var info = []
info.append(tab(indent_level, op_type))
return info.join('')
static func op_info(op):
if not Operator.is_op(op) :
printerr('%s is not a valid operator' % op.name)
#determine associativity and operands
# each operand has
var TokenType = Constants.TokenType
match op:
TokenType.Not, TokenType.UnaryMinus:
return OperatorInfo.new(Associativity.Right, 30, 1)
TokenType.Multiply, TokenType.Divide, TokenType.Modulo:
return OperatorInfo.new(Associativity.Left, 20, 2)
TokenType.Add, TokenType.Minus:
return OperatorInfo.new(Associativity.Left, 15, 2)
TokenType.GreaterThan, TokenType.LessThan, TokenType.GreaterThanOrEqualTo, TokenType.LessThanOrEqualTo:
return OperatorInfo.new(Associativity.Left, 10, 2)
TokenType.EqualTo, TokenType.EqualToOrAssign, TokenType.NotEqualTo:
return OperatorInfo.new(Associativity.Left, 5, 2)
TokenType.And:
return OperatorInfo.new(Associativity.Left, 4, 2)
TokenType.Or:
return OperatorInfo.new(Associativity.Left, 3, 2)
TokenType.Xor:
return OperatorInfo.new(Associativity.Left, 2, 2)
_:
printerr('Unknown operator: %s' % op.name)
return null
static func is_op(type):
return type in op_types()
static func op_types():
return [
Constants.TokenType.Not,
Constants.TokenType.UnaryMinus,
Constants.TokenType.Add,
Constants.TokenType.Minus,
Constants.TokenType.Divide,
Constants.TokenType.Multiply,
Constants.TokenType.Modulo,
Constants.TokenType.EqualToOrAssign,
Constants.TokenType.EqualTo,
Constants.TokenType.GreaterThan,
Constants.TokenType.GreaterThanOrEqualTo,
Constants.TokenType.LessThan,
Constants.TokenType.LessThanOrEqualTo,
Constants.TokenType.NotEqualTo,
Constants.TokenType.And,
Constants.TokenType.Or,
Constants.TokenType.Xor
]
class OperatorInfo:
var associativity
var precedence = -1
var arguments = -1
func _init(associativity, precedence, arguments):
self.associativity = associativity
self.precedence = precedence
self.arguments = arguments
class Clause:
var expression
var statements = [] #Statement
func _init(expression = null, statements = []):
self.expression = expression
self.statements = statements
func tree_string(indent_level):
var info = []
if expression != null:
info.append(expression.tree_string(indent_level))
info.append(tab(indent_level, '{'))
for statement in statements:
info.append(statement.tree_string(indent_level + 1))
info.append(tab(indent_level,'}'))
return PoolStringArray(info).join('')
func tab(indent_level, input, newline = true):
return '%*s| %s%s' % [indent_level * 2, '', input, '' if !newline else '\n']