diff --git a/addons/Wol/Wol.gd b/addons/Wol/Wol.gd index a07398f..72aa4e4 100644 --- a/addons/Wol/Wol.gd +++ b/addons/Wol/Wol.gd @@ -3,10 +3,15 @@ extends Node class_name Wol signal node_started(node) +signal node_finished(node) + +# NOTE: Warning is ignored because they get call_deferred +# warning-ignore:unused_signal signal line(line) +# warning-ignore:unused_signal signal options(options) +# warning-ignore:unused_signal signal command(command) -signal node_completed(node) signal started signal finished @@ -62,14 +67,12 @@ func init_dialogue(): dialogue.set_program(program) func set_path(_path): - if not Engine.editor_hint: - var file = File.new() - file.open(_path, File.READ) - var source = file.get_as_text() - file.close() - program = WolCompiler.compile_string(source, _path) path = _path + if not Engine.editor_hint: + var compiler = WolCompiler.new(path) + program = compiler.compile() + func _handle_line(line): call_deferred('emit_signal', 'line', line) if auto_show_options \ @@ -103,7 +106,7 @@ func _handle_node_start(node): dialogue._visitedNodeCount[node] += 1 func _handle_node_complete(node): - emit_signal('node_completed', node) + emit_signal('node_finished', node) return Constants.HandlerState.ContinueExecution func select_option(id): diff --git a/addons/Wol/core/compiler/compiler.gd b/addons/Wol/core/compiler/compiler.gd index 575e4b8..aa6b17c 100644 --- a/addons/Wol/core/compiler/compiler.gd +++ b/addons/Wol/core/compiler/compiler.gd @@ -4,158 +4,152 @@ class_name Compiler const Constants = preload('res://addons/Wol/core/constants.gd') const Lexer = preload('res://addons/Wol/core/compiler/lexer.gd') const Program = preload('res://addons/Wol/core/program.gd') +const Parser = preload('res://addons/Wol/core/compiler/parser.gd') -#patterns -const INVALIDTITLENAME = '[\\[<>\\]{}\\|:\\s#\\$]' +const INVALID_TITLE = '[\\[<>\\]{}\\|:\\s#\\$]' -#ERROR Codes const NO_ERROR = 0x00 const LEXER_FAILURE = 0x01 const PARSER_FAILURE = 0x02 const INVALID_HEADER = 0x04 -const DUPLICATE_NODES_IN_PROGRAM = 0x08 const ERR_COMPILATION_FAILED = 0x10 -var _errors : int -var _last_error : int +var source = '' +var filename = '' -#-----Class vars -var _current_node : Program.WolNode -var _raw_text : bool -var _file_name : String -var _contains_implicit_string_tags : bool -var _label_count : int = 0 +var _current_node +var _contains_implicit_string_tags = false +var _label_count = 0 -# -var _string_table : Dictionary = {} -var _string_count : int = 0 -# -var _tokens : Dictionary = {} +var _string_table = {} +var _string_count = 0 -static func compile_string(source: String, filename: String): - var Parser = load('res://addons/Wol/core/compiler/parser.gd') - var Compiler = load('res://addons/Wol/core/compiler/compiler.gd') +func _init(_filename, _source = null): + filename = _filename - var compiler = Compiler.new() - compiler._file_name = filename + if not _filename and _source: + self.source = _source + else: + var file = File.new() + file.open(_filename, File.READ) + self.source = file.get_as_text() + file.close() - #--------------Nodes - var header_sep : RegEx = RegEx.new() +func compile(): + var header_sep = RegEx.new() + var header_property = RegEx.new() header_sep.compile('---(\r\n|\r|\n)') - var header_property : RegEx = RegEx.new() header_property.compile('(?.*): *(?.*)') - assert(not not header_sep.search(source), 'No headers found') + assert(header_sep.search(source), 'No headers found!') - var line_number: int = 0 - - var source_lines : Array = source.split('\n',false) - for i in range(source_lines.size()): - source_lines[i] = source_lines[i].strip_edges(false,true) + var line_number = 0 - var parsed_nodes : Array = [] + var source_lines = source.split('\n', false) + for i in range(source_lines.size()): + source_lines[i] = source_lines[i].strip_edges(false, true) + + var parsed_nodes = [] while line_number < source_lines.size(): - - var title : String - var body : String + var title = '' + var body = '' - #get title + # Parse header while true: - var line : String = source_lines[line_number] - line_number+=1 + var line = source_lines[line_number] + line_number += 1 - if !line.empty(): + if not line.empty(): var result = header_property.search(line) - if result != null : - var field : String = result.get_string('field') - var value : String = result.get_string('value') + if result != null: + var field = result.get_string('field') + var value = result.get_string('value') if field == 'title': - assert(not ' ' in value, 'No space allowed in title "%s", correct to "%s"' % [value, value.replace(' ','')]) - title = value + var regex = RegEx.new() + regex.compile(INVALID_TITLE) + assert(not regex.search(value), 'Invalid characters in title "%s", correct to "%s"' % [value, regex.sub(value, '', true)]) - if(line_number >= source_lines.size() || source_lines[line_number] == '---'): + title = value + # TODO: Implement position, color and tags + + if line_number >= source_lines.size() or line == '---': break - - line_number+=1 + line_number += 1 - #past header - var body_lines : PoolStringArray = [] + # past header + var body_lines = [] - while line_number < source_lines.size() and source_lines[line_number]!='===': + while line_number < source_lines.size() and source_lines[line_number] != '===': body_lines.append(source_lines[line_number]) - line_number+=1 + line_number += 1 - line_number+=1 + line_number += 1 - body = body_lines.join('\n') - var lexer = Lexer.new() - var tokens = lexer.tokenize(body, title, filename) + body = PoolStringArray(body_lines).join('\n') - var parser = Parser.new(tokens, title) + var lexer = Lexer.new(filename, title, body) + var tokens = lexer.tokenize() + + var parser = Parser.new(title, tokens) var parser_node = parser.parse_node() parser_node.name = title + # parser_node.tags = title parsed_nodes.append(parser_node) - while line_number < source_lines.size() && source_lines[line_number].empty(): - line_number+=1 - #--- End parsing nodes--- + while line_number < source_lines.size() and source_lines[line_number].empty(): + line_number += 1 var program = Program.new() - #compile nodes for node in parsed_nodes: - compiler.compile_node(program, node) + compile_node(program, node) - for key in compiler._string_table: - program.strings[key] = compiler._string_table[key] + for key in _string_table: + program.strings[key] = _string_table[key] return program func compile_node(program, parsed_node): - if program.nodes.has(parsed_node.name): - emit_error(DUPLICATE_NODES_IN_PROGRAM) - printerr('Duplicate node in program: %s' % parsed_node.name) + assert(not program.nodes.has(parsed_node.name), 'Duplicate node in program: %s' % parsed_node.name) + + var node_compiled = Program.WolNode.new() + + node_compiled.name = parsed_node.name + node_compiled.tags = parsed_node.tags + + if parsed_node.source != null and not parsed_node.source.empty(): + node_compiled.source_id = register_string( + parsed_node.source, + parsed_node.name, + 'line:' + parsed_node.name, + 0, + [] + ) else: - var node_compiled = Program.WolNode.new() + var start_label = register_label() + emit(Constants.ByteCode.Label,node_compiled,[Program.Operand.new(start_label)]) - node_compiled.name = parsed_node.name - node_compiled.tags = parsed_node.tags + for statement in parsed_node.statements: + generate_statement(node_compiled, statement) + + var dangling_options = false + for instruction in node_compiled.instructions: + if instruction.operation == Constants.ByteCode.AddOption: + dangling_options = true + if instruction.operation == Constants.ByteCode.ShowOptions: + dangling_options = false - #raw text - if parsed_node.source != null && !parsed_node.source.empty(): - node_compiled.source_id = register_string(parsed_node.source,parsed_node.name, - 'line:'+parsed_node.name, 0, []) + if dangling_options: + emit(Constants.ByteCode.ShowOptions, node_compiled) + emit(Constants.ByteCode.RunNode, node_compiled) else: - #compile node - var start_label : String = register_label() - emit(Constants.ByteCode.Label,node_compiled,[Program.Operand.new(start_label)]) + emit(Constants.ByteCode.Stop, node_compiled) - for statement in parsed_node.statements: - generate_statement(node_compiled,statement) - - - #add options - #todo: add parser flag - - var dangling_options = false - for instruction in node_compiled.instructions : - if instruction.operation == Constants.ByteCode.AddOption: - dangling_options = true - if instruction.operation == Constants.ByteCode.ShowOptions: - dangling_options = false - - if dangling_options: - emit(Constants.ByteCode.ShowOptions, node_compiled) - emit(Constants.ByteCode.RunNode, node_compiled) - else: - emit(Constants.ByteCode.Stop, node_compiled) - - - program.nodes[node_compiled.name] = node_compiled + program.nodes[node_compiled.name] = node_compiled func register_string(text:String,node_name:String,id:String='',line_number:int=-1,tags:Array=[])->String: var line_id_used : String @@ -163,7 +157,7 @@ func register_string(text:String,node_name:String,id:String='',line_number:int=- var implicit : bool if id.empty(): - line_id_used = '%s-%s-%d' % [self._file_name,node_name,self._string_count] + line_id_used = '%s-%s-%d' % [self.filename,node_name,self._string_count] self._string_count+=1 #use this when we generate implicit tags @@ -176,7 +170,7 @@ func register_string(text:String,node_name:String,id:String='',line_number:int=- line_id_used = id implicit = false - var string_info = Program.Line.new(text,node_name,line_number,_file_name,implicit,tags) + var string_info = Program.Line.new(text,node_name,line_number,filename,implicit,tags) #add to string table and return id self._string_table[line_id_used] = string_info @@ -202,18 +196,11 @@ func emit(bytecode, node = _current_node, operands = []): node.labels[instruction.operands[0].value] = node.instructions.size()-1 -func get_string_tokens()->Array: - return [] - -#compile header func generate_header(): pass -#compile instructions for statements -#this will walk through all child branches -#of the parse tree func generate_statement(node,statement): - # print('generating statement') + match statement.type: Constants.StatementTypes.CustomCommand: generate_custom_command(node,statement.custom_command) @@ -230,8 +217,7 @@ func generate_statement(node,statement): Constants.StatementTypes.Line: generate_line(node,statement,statement.line) _: - emit_error(ERR_COMPILATION_FAILED) - printerr('illegal statement type [%s]- could not generate code' % statement.type) + assert(false, 'Illegal statement type [%s]. Could not generate code.' % statement.type) #compile instructions for custom commands func generate_custom_command(node,command): @@ -306,7 +292,6 @@ func generate_shortcut_group(node,shortcut_group): emit(Constants.ByteCode.Pop,node) - #compile instructions for block #blocks are just groups of statements func generate_block(node,statements:Array=[]): @@ -347,7 +332,7 @@ func generate_option(node,option): # print('generating option') var destination : String = option.destination - if option.label == null || option.label.empty(): + if option.label == null or option.label.empty(): #jump to another node emit(Constants.ByteCode.RunNode,node,[Program.Operand.new(destination)]) else : @@ -401,7 +386,7 @@ func generate_assignment(node,assignment): #compile expression instructions func generate_expression(node,expression): # print('generating expression') - #expression = value || func call + #expression = value or func call match expression.type: Constants.ExpressionType.Value: generate_value(node,expression.value) @@ -438,22 +423,6 @@ func generate_value(node,value): _: printerr('Unrecognized valuenode type: %s' % value.value.type) -#get the error flags -func get_errors()->int: - return _errors - -#get the last error code reported -func get_last_error()->int: - return _last_error - -func clear_errors()->void: - _errors = NO_ERROR - _last_error = NO_ERROR - -func emit_error(error : int)->void: - _last_error = error - _errors |= _last_error - static func print_tokens(tokens:Array=[]): var list : PoolStringArray = [] list.append('\n') diff --git a/addons/Wol/core/compiler/lexer.gd b/addons/Wol/core/compiler/lexer.gd index 13412bb..4f84176 100644 --- a/addons/Wol/core/compiler/lexer.gd +++ b/addons/Wol/core/compiler/lexer.gd @@ -1,4 +1,5 @@ extends Object +class_name Lexer const Constants = preload('res://addons/Wol/core/constants.gd') @@ -23,16 +24,22 @@ var WHITESPACE : String = '\\s*' var _states : Dictionary = {} var _defaultState : LexerState - var _currentState : LexerState var _indentStack : Array = [] var _shouldTrackIndent : bool = false +var filename = '' +var title = '' +var text = '' -func _init(): +func _init(_filename, _title, _text): create_states() + filename = _filename + title = _title + text = _text + func create_states(): var patterns : Dictionary = {} patterns[Constants.TokenType.Text] = ['.*', 'any text'] @@ -43,7 +50,7 @@ func create_states(): patterns[Constants.TokenType.LeftParen] = ['\\(', 'left parenthesis ('] patterns[Constants.TokenType.RightParen] = ['\\)', 'right parenthesis )'] patterns[Constants.TokenType.EqualTo] = ['(==|is(?!\\w)|eq(?!\\w))', '"=", "is" or "eq"'] - patterns[Constants.TokenType.EqualToOrAssign] = ['(=|to(?!\\w))', 'equal to "=" or assign "="'] + patterns[Constants.TokenType.EqualToOrAssign] = ['(=|to(?!\\w))', '"=" or "to"'] patterns[Constants.TokenType.NotEqualTo] = ['(\\!=|neq(?!\\w))', '"!=" or "neq"'] patterns[Constants.TokenType.GreaterThanOrEqualTo] = ['(\\>=|gte(?!\\w))', '">=" or "gte"'] patterns[Constants.TokenType.GreaterThan] = ['(\\>|gt(?!\\w))', '">" or "gt"'] @@ -81,9 +88,9 @@ func create_states(): patterns[Constants.TokenType.ShortcutOption] = ['\\-\\>\\s*', '"->"'] #compound states - var shortcut_option : String= SHORTCUT + DASH + OPTION + var shortcut_option : String = SHORTCUT + DASH + OPTION var shortcut_option_tag : String = shortcut_option + DASH + TAG - var command_or_expression : String= COMMAND + DASH + OR + DASH + EXPRESSION + var command_or_expression : String = COMMAND + DASH + OR + DASH + EXPRESSION var link_destination : String = LINK + DASH + DESTINATION _states = {} @@ -91,7 +98,7 @@ func create_states(): _states[BASE] = LexerState.new(patterns) _states[BASE].add_transition(Constants.TokenType.BeginCommand,COMMAND,true) _states[BASE].add_transition(Constants.TokenType.OptionStart,LINK,true) - _states[BASE].add_transition(Constants.TokenType.ShortcutOption,shortcut_option) + _states[BASE].add_transition(Constants.TokenType.ShortcutOption, shortcut_option) _states[BASE].add_transition(Constants.TokenType.TagMarker,TAG,true) _states[BASE].add_text_rule(Constants.TokenType.Text) @@ -112,7 +119,7 @@ func create_states(): _states[COMMAND].add_transition(Constants.TokenType.ElseToken) _states[COMMAND].add_transition(Constants.TokenType.ElseIf,EXPRESSION) _states[COMMAND].add_transition(Constants.TokenType.EndIf) - _states[COMMAND].add_transition(Constants.TokenType.Set,ASSIGNMENT) + _states[COMMAND].add_transition(Constants.TokenType.Set, ASSIGNMENT) _states[COMMAND].add_transition(Constants.TokenType.EndCommand,BASE,true) _states[COMMAND].add_transition(Constants.TokenType.Identifier,command_or_expression) _states[COMMAND].add_text_rule(Constants.TokenType.Text) @@ -173,9 +180,9 @@ func create_states(): for stateKey in _states.keys(): _states[stateKey].stateName = stateKey -func tokenize(text, title, filename): +func tokenize(): _indentStack.clear() - _indentStack.push_front(IntBoolPair.new(0,false)) + _indentStack.push_front(IntBoolPair.new(0, false)) _shouldTrackIndent = false var tokens : Array = [] @@ -188,7 +195,7 @@ func tokenize(text, title, filename): var line_number : int = 1 for line in lines: - tokens += tokenize_line(line, line_number, title, filename) + tokens += tokenize_line(line, line_number) line_number += 1 var endOfInput = Token.new( @@ -201,14 +208,14 @@ func tokenize(text, title, filename): return tokens -func tokenize_line(line, line_number, title, filename): +func tokenize_line(line, line_number): var tokenStack : Array = [] var freshLine = line.replace('\t',' ').replace('\r','') #record indentation var indentation = line_indentation(line) - var prevIndentation : IntBoolPair = _indentStack.front() + var prevIndentation = _indentStack.front() if _shouldTrackIndent && indentation > prevIndentation.key: #we add an indenation token to record indent level @@ -259,7 +266,7 @@ func tokenize_line(line, line_number, title, filename): var tokenText : String - if rule.tokenType == Constants.TokenType.Text: + if rule.token_type == Constants.TokenType.Text: #if this is text then we back up to the most recent #delimiting token and treat everything from there as text. @@ -289,29 +296,29 @@ func tokenize_line(line, line_number, title, filename): column += tokenText.length() #pre-proccess string - if rule.tokenType == Constants.TokenType.Str: + if rule.token_type == Constants.TokenType.Str: tokenText = tokenText.substr(1, tokenText.length() - 2) tokenText = tokenText.replace('\\\\', '\\') tokenText = tokenText.replace('\\\'','\'') var token = Token.new( - rule.tokenType, + rule.token_type, _currentState, filename, line_number, column, tokenText ) - token.delimitsText = rule.delimitsText + token.delimits_text = rule.delimits_text tokenStack.push_front(token) - if rule.enterState != null and rule.enterState.length() > 0: - if not _states.has(rule.enterState): - printerr('State[%s] not known - line(%s) col(%s)' % [rule.enterState, line_number, column]) + if rule.enter_state != null and rule.enter_state.length() > 0: + if not _states.has(rule.enter_state): + printerr('State[%s] not known - line(%s) col(%s)' % [rule.enter_state, line_number, column]) return [] - enter_state(_states[rule.enterState]) + enter_state(_states[rule.enter_state]) if _shouldTrackIndent: if _indentStack.front().key < indentation: @@ -323,7 +330,7 @@ func tokenize_line(line, line_number, title, filename): if not matched: var rules = [] for rule in _currentState.rules: - rules.append('"%s" (%s)' % [Constants.token_type_name(rule.tokenType), rule.human_readable_identifier]) + rules.append('"%s" (%s)' % [Constants.token_type_name(rule.token_type), rule.human_readable_identifier]) var error_data = [ PoolStringArray(rules).join(', ') if rules.size() == 1 else PoolStringArray(rules.slice(0, rules.size() - 2)).join(', ') + ' or %s' % rules[-1], @@ -368,17 +375,17 @@ class Token: var column = -1 var text = '' - var delimitsText = false + var delimits_text = false var paramCount = -1 var lexerState = '' - func _init(type, state, filename, line_number = -1, column = -1, value = ''): - self.type = type - self.lexerState = state.stateName - self.filename = filename - self.line_number = line_number - self.column = column - self.value = value + func _init(_type, _state, _filename, _line_number = -1, _column = -1, _value = ''): + type = _type + lexerState = _state.stateName + filename = _filename + line_number = _line_number + column = _column + value = _value func _to_string(): return '%s (%s) at %s:%s (state: %s)' % [Constants.token_type_name(type),value,line_number,column,lexerState] @@ -390,8 +397,8 @@ class LexerState: var rules : Array = [] var track_indent : bool = false - func _init(patterns): - self.patterns = patterns + func _init(_patterns): + patterns = _patterns func add_transition(type : int, state : String = '',delimitText : bool = false)->Rule: var pattern = '\\G%s' % patterns[type][0] @@ -407,48 +414,49 @@ class LexerState: var delimiters:Array = [] for rule in rules: - if rule.delimitsText: + if rule.delimits_text: delimiters.append('%s' % rule.regex.get_pattern().substr(2)) var pattern = '\\G((?!%s).)*' % [PoolStringArray(delimiters).join('|')] var rule : Rule = add_transition(type,state) rule.regex = RegEx.new() rule.regex.compile(pattern) - rule.isTextRule = true + rule.is_text_rule = true return rule func contains_text_rule()->bool: for rule in rules: - if rule.isTextRule: + if rule.is_text_rule: return true return false - class Rule: var regex : RegEx - var enterState : String - var tokenType : int - var isTextRule : bool - var delimitsText : bool + var enter_state : String + var token_type : int + var is_text_rule : bool + var delimits_text : bool var human_readable_identifier = '' - func _init(type : int , regex : String, human_readable_identifier, enterState : String, delimitsText:bool): - self.tokenType = type - self.regex = RegEx.new() - self.regex.compile(regex) - self.human_readable_identifier = human_readable_identifier - self.enterState = enterState - self.delimitsText = delimitsText + func _init(_type, _regex, _human_readable_identifier, _enter_state, _delimits_text): + token_type = _type + + regex = RegEx.new() + regex.compile(_regex) + + human_readable_identifier = _human_readable_identifier + enter_state = _enter_state + delimits_text = _delimits_text func _to_string(): - return '[Rule : %s (%s) - %s]' % [Constants.token_type_name(tokenType), human_readable_identifier, regex] + return '[Rule : %s (%s) - %s]' % [Constants.token_type_name(token_type), human_readable_identifier, regex] class IntBoolPair: - var key : int - var value : bool + var key = -1 + var value = false - func _init(key:int,value:bool): - self.key = key - self.value = value + func _init(_key, _value): + key = _key + value = _value diff --git a/addons/Wol/core/compiler/parser.gd b/addons/Wol/core/compiler/parser.gd index 52aa620..2cf626c 100644 --- a/addons/Wol/core/compiler/parser.gd +++ b/addons/Wol/core/compiler/parser.gd @@ -3,12 +3,12 @@ extends Object const Constants = preload('res://addons/Wol/core/constants.gd') const Lexer = preload('res://addons/Wol/core/compiler/lexer.gd') -var _tokens = [] +var tokens = [] var title = '' -func _init(tokens, title): - self._tokens = tokens - self.title = title +func _init(_title, _tokens): + title = _title + tokens = _tokens enum Associativity { Left, @@ -20,7 +20,7 @@ func parse_node(): return WolNode.new('Start', null, self) func next_symbol_is(valid_types): - var type = self._tokens.front().type + var type = self.tokens.front().type for valid_type in valid_types: if type == valid_type: return true @@ -28,14 +28,14 @@ func next_symbol_is(valid_types): # NOTE:0 look ahead for `<<` and `else` func next_symbols_are(valid_types): - var temporary = [] + _tokens + 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 + var token = tokens.pop_front() as Lexer.Token if token_types.size() == 0: if token.type == Constants.TokenType.EndOfInput: @@ -51,7 +51,6 @@ func expect_symbol(token_types = []): 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' \ @@ -74,9 +73,6 @@ func expect_symbol(token_types = []): 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 = '' @@ -84,21 +80,22 @@ class ParseNode: var line_number = -1 var tags = [] - func _init(parent, parser): - self.parent = parent + func _init(_parent, _parser): + parent = _parent - var tokens = parser.tokens() as Array + 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): + func tree_string(_indent_level): return 'Not_implemented' - func tags_to_string(indent_level): - return '%s' % 'TAGSNOTIMPLEMENTED' + func tags_to_string(_indent_level): + return 'TAGSNOTIMPLEMENTED' func get_node_parent(): var node = self @@ -111,10 +108,6 @@ class ParseNode: 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 = '' @@ -123,7 +116,7 @@ class WolNode extends ParseNode: func _init(name, parent, parser).(parent, parser): self.name = name - while parser.tokens().size() > 0 \ + while parser.tokens.size() > 0 \ and not parser.next_symbol_is([Constants.TokenType.Dedent, Constants.TokenType.EndOfInput]): statements.append(Statement.new(self, parser)) @@ -180,7 +173,7 @@ class Statement extends ParseNode: type = Type.Line else: - printerr('Expected a statement but got %s instead. (probably an imbalanced if statement)' % parser.tokens().front()._to_string()) + printerr('Expected a statement but got %s instead. (probably an imbalanced if statement)' % parser.tokens.front()._to_string()) var tags = [] @@ -242,9 +235,8 @@ class CustomCommand extends ParseNode: 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) + expression = ExpressionNode.parse(self, p) type = Type.Expression - self.expression = expression else: #otherwise evaluuate command type = Type.ClientCommand @@ -548,50 +540,43 @@ class ValueNode extends ParseNode: 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 + var parameters = [] - func _init(parent, parser, value, function = '', params = []).(parent, parser): + func _init(parent, parser, _value, _function = '', _parameters = []).(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 + if _value != null: + type = Constants.ExpressionType.Value + value = _value + else: + type = Constants.ExpressionType.FunctionCall + function = _function + parameters = _parameters 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(tab(indent_level,'Func[%s - parameters(%s)]:{'%[function, parameters.size()])) + for param in parameters: 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 + # using Djikstra's shunting-yard algorithm to convert stream of expresions into postfix notation, + # & then build a tree of expressions static func parse(parent, parser): var rpn = [] var op_stack = [] - #track params + #track parameters var func_stack = [] var valid_types = [ @@ -612,7 +597,7 @@ class ExpressionNode extends ParseNode: var last #read expression content - while parser.tokens().size() > 0 && parser.next_symbol_is(valid_types): + while parser.tokens.size() > 0 && parser.next_symbol_is(valid_types): var next = parser.expect_symbol(valid_types) if next.type == Constants.TokenType.Variable \ @@ -646,7 +631,7 @@ class ExpressionNode extends ParseNode: # 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) + printerr('Expected Expression : %s' % parser.tokens.front().name) #find the closest function on stack #increment parameters @@ -698,7 +683,7 @@ class ExpressionNode extends ParseNode: op_stack.pop_back() if op_stack.back().type == Constants.TokenType.Identifier: #function call - #last token == left paren this == no params + #last token == left paren this == no parameters #else #we have more than 1 param if last.type != Constants.TokenType.LeftParen: @@ -731,36 +716,35 @@ class ExpressionNode extends ParseNode: 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()) + var function_parameters = [] + for _i in range(info.arguments): + function_parameters.append(eval_stack.pop_back()) - params.invert() + function_parameters.invert() - var function = get_func_name(next.type) - - var expression = ExpressionNode.new(parent, parser, null, function, params) + var function_name = get_func_name(next.type) + var expression = ExpressionNode.new(parent, parser, null, function_name, function_parameters) eval_stack.append(expression) + # A function call elif next.type == Constants.TokenType.Identifier: - #function call + var function_name = next.value - var function = next.value - - var params = []#ExpressionNode - for i in range(next.param_count): - - params.append(eval_stack.pop_back()) + var function_parameters = [] + for _i in range(next.param_count): + function_parameters.append(eval_stack.pop_back()) - params.invert() + function_parameters.invert() - var expression = ExpressionNode.new(parent, parser, null, function, params) + var expression = ExpressionNode.new(parent, parser, null, function_name, function_parameters) eval_stack.append(expression) - else: #raw value - var value = ValueNode.new(parent, parser, next) - var expression = ExpressionNode.new(parent, parser, value) + + # A raw value + else: + var raw_value = ValueNode.new(parent, parser, next) + var expression = ExpressionNode.new(parent, parser, raw_value) eval_stack.append(expression) @@ -769,45 +753,39 @@ class ExpressionNode extends ParseNode: 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): + static func get_func_name(_type): var string = '' for key in Constants.TokenType.keys(): - if Constants.TokenType[key] == type: + if Constants.TokenType[key] == _type: return key return string - static func is_apply_precedence(type, operator_stack): + 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!') + if not Operator.is_op(_type): + assert(false, '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 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 + return \ + (first_info.associativity == Associativity.Left \ + and first_info.precedence <= second_info.precedence) \ + or \ + (first_info.associativity == Associativity.Right \ + and first_info.precedence < second_info.precedence) class Assignment extends ParseNode: - var destination var value var operation @@ -845,15 +823,13 @@ class Assignment extends ParseNode: ] 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 + func _init(parent, parser, _op_type = null).(parent, parser): + if _op_type == null : + op_type = parser.expect_symbol(Operator.op_types()).type else: - self.op_type = op_type + op_type = _op_type func tree_string(indent_level): var info = [] @@ -923,18 +899,18 @@ class OperatorInfo: var precedence = -1 var arguments = -1 - func _init(associativity, precedence, arguments): - self.associativity = associativity - self.precedence = precedence - self.arguments = arguments + func _init(_associativity, _precedence, _arguments): + associativity = _associativity + precedence = _precedence + arguments = _arguments class Clause: var expression var statements = [] #Statement - func _init(expression = null, statements = []): - self.expression = expression - self.statements = statements + func _init(_expression = null, _statements = []): + expression = _expression + statements = _statements func tree_string(indent_level): var info = [] diff --git a/addons/Wol/core/dialogue.gd b/addons/Wol/core/dialogue.gd index b46f461..a3eb228 100644 --- a/addons/Wol/core/dialogue.gd +++ b/addons/Wol/core/dialogue.gd @@ -49,6 +49,7 @@ func set_node(name = DEFAULT_START): _vm.set_node(name) func start(): + print('got here') if _vm.executionState == Constants.ExecutionState.Stopped: _vm.resume() diff --git a/addons/Wol/core/function_info.gd b/addons/Wol/core/function_info.gd index 5805125..3e94ac2 100644 --- a/addons/Wol/core/function_info.gd +++ b/addons/Wol/core/function_info.gd @@ -1,39 +1,35 @@ extends Object var Value : GDScript = load("res://addons/Wol/core/value.gd") -#name of the function -var name : String +var name = '' +# NOTE: -1 means variable arguments +var parameter_count = 0 +var function +var returns_value = false -#param count of this function -# -1 means variable arguments -var paramCount : int = 0 -#function implementation -var function : FuncRef -var returnsValue : bool = false +func _init(_name, _parameter_count, _function = null, _returns_value = false): + name = _name + parameter_count = _parameter_count + function = _function + returns_value = _returns_value -func _init(name: String, paramCount: int, function: FuncRef = null, returnsValue: bool = false): - self.name = name - self.paramCount = paramCount - self.function = function - self.returnsValue = returnsValue - -func invoke(params = []): +func invoke(parameters = []): var length = 0 - if params != null: - length = params.size() + if parameters != null: + length = parameters.size() if check_param_count(length): - if returnsValue: + if returns_value: if length > 0: - return Value.new(function.call_funcv(params)) + return Value.new(function.call_funcv(parameters)) else: return Value.new(function.call_func()) else: if length > 0: - function.call_funcv(params) + function.call_funcv(parameters) else : function.call_func() return null -func check_param_count(paramCount: int): - return self.paramCount == paramCount || self.paramCount == -1 +func check_param_count(_parameter_count): + return parameter_count == _parameter_count or parameter_count == -1 diff --git a/addons/Wol/core/program.gd b/addons/Wol/core/program.gd index 8191ad3..a87736a 100644 --- a/addons/Wol/core/program.gd +++ b/addons/Wol/core/program.gd @@ -15,28 +15,28 @@ class Line: var implicit = false var meta = [] - func _init(text, node_name, line_number, file_name, implicit, meta): - self.text = text - self.node_name = node_name - self.file_name = file_name - self.implicit = implicit - self.meta = meta + func _init(_text, _node_name, _line_number, _file_name, _implicit, _meta): + text = _text + node_name = _node_name + file_name = _file_name + implicit = _implicit + meta = _meta class Option: var line var id = -1 var destination = '' - func _init(line, id, destination): - self.line = line - self.id = id - self.destination = destination + func _init(_line, _id, _destination): + line = _line + id = _id + destination = _destination class Command: var command = '' - func _init(command): - self.command = command + func _init(_command): + command = _command class WolNode: var name = '' @@ -55,15 +55,15 @@ class WolNode: source_id = other.source_id func equals(other): - if other.get_script() != self.get_script(): + if other.get_script() != get_script(): return false - if other.name != self.name: + if other.name != name: return false - if other.instructions != self.instructions: + if other.instructions != instructions: return false - if other.label != self.label: + if other.labels != labels: return false - if other.sourceId != self.sourceId: + if other.source_id != source_id: return false return true @@ -82,33 +82,33 @@ class Operand: var value var type - func _init(value): - if typeof(value) == TYPE_OBJECT and value.get_script() == self.get_script(): - set_value(value.value) + func _init(_value): + if typeof(_value) == TYPE_OBJECT and _value.get_script() == get_script(): + set_value(_value.value) else: - set_value(value) + set_value(_value) - func set_value(value): - match typeof(value): + func set_value(_value): + match typeof(_value): TYPE_REAL,TYPE_INT: - set_number(value) + set_number(_value) TYPE_BOOL: - set_boolean(value) + set_boolean(_value) TYPE_STRING: - set_string(value) + set_string(_value) - func set_boolean(value): - _value(value) + func set_boolean(_value): + value = _value type = ValueType.BooleanValue return self - func set_string(value): - _value(value) + func set_string(_value): + value = _value type = ValueType.StringValue return self - func set_number(value): - _value(value) + func set_number(_value): + value = _value type = ValueType.FloatValue return self @@ -122,9 +122,6 @@ class Operand: func _to_string(): return "Operand[%s:%s]" % [type, value] - func _value(value): - self.value = value - class Instruction: var operation = -1 var operands = [] @@ -134,9 +131,6 @@ class Instruction: self.operation = other.operation self.operands += other.operands - func dump(program, library): - return "InstructionInformation:NotImplemented" - func _to_string(): return Constants.bytecode_name(operation) + ':' + operands as String diff --git a/addons/Wol/core/virtual_machine.gd b/addons/Wol/core/virtual_machine.gd index b0eacc9..a279fc8 100644 --- a/addons/Wol/core/virtual_machine.gd +++ b/addons/Wol/core/virtual_machine.gd @@ -219,27 +219,27 @@ func run_instruction(instruction)->bool: var function = _dialogue.library.get_function(functionName) - var expectedParamCount : int = function.paramCount - var actualParamCount : int = _state.pop_value().as_number() + var expected_parameter_count : int = function.paramCount + var actual_parameter_count : int = _state.pop_value().as_number() #if function takes in -1 params disregard #expect the compiler to have placed the number of params #at the top of the stack - if expectedParamCount == -1: - expectedParamCount = actualParamCount + if expected_parameter_count == -1: + expected_parameter_count = actual_parameter_count - if expectedParamCount != actualParamCount: + if expected_parameter_count != actual_parameter_count: printerr('Function %s expected %d parameters but got %d instead' %[functionName, - expectedParamCount,actualParamCount]) + expected_parameter_count,actual_parameter_count]) return false var result - if actualParamCount == 0: + if actual_parameter_count == 0: result = function.invoke() else: var params : Array = []#value - for i in range(actualParamCount): + for _i in range(actual_parameter_count): params.push_front(_state.pop_value()) result = function.invoke(params) @@ -352,6 +352,6 @@ class SimpleEntry: var key var value - func _init(key, value): - self.key = key - self.value = value + func _init(_key, _value): + key = _key + value = _value diff --git a/project.godot b/project.godot index 7b73ad5..5ae01c0 100644 --- a/project.godot +++ b/project.godot @@ -20,6 +20,11 @@ _global_script_classes=[ { "path": "res://addons/Wol/core/constants.gd" }, { "base": "Object", +"class": "Lexer", +"language": "GDScript", +"path": "res://addons/Wol/core/compiler/lexer.gd" +}, { +"base": "Object", "class": "Program", "language": "GDScript", "path": "res://addons/Wol/core/program.gd" @@ -32,6 +37,7 @@ _global_script_classes=[ { _global_script_class_icons={ "Compiler": "", "Constants": "", +"Lexer": "", "Program": "", "Wol": "" } @@ -42,6 +48,12 @@ config/name="Wol" run/main_scene="res://Dialogue.tscn" config/icon="res://icon.png" +[debug] + +gdscript/warnings/treat_warnings_as_errors=true +gdscript/warnings/exclude_addons=false +gdscript/warnings/return_value_discarded=false + [editor_plugins] enabled=PoolStringArray( "res://addons/Wol/plugin.cfg" )