made several fixes and started work on editor
This commit is contained in:
parent
0fd71a9940
commit
4cb93851b1
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/addons/Wol/editor/WolEditor.tscn filter=lfs diff=lfs merge=lfs -text
|
|
@ -8,17 +8,20 @@ func _ready():
|
||||||
$Ship/DialogueStarter.connect('body_entered', self, '_on_player_near_dialogue', [$Ship, true])
|
$Ship/DialogueStarter.connect('body_entered', self, '_on_player_near_dialogue', [$Ship, true])
|
||||||
$Ship/DialogueStarter.connect('body_exited', self, '_on_player_near_dialogue', [$Ship, false])
|
$Ship/DialogueStarter.connect('body_exited', self, '_on_player_near_dialogue', [$Ship, false])
|
||||||
|
|
||||||
|
$Dialogue/Wol.connect('finished', self, '_on_finished')
|
||||||
|
|
||||||
func _on_player_near_dialogue(_player, node, entered):
|
func _on_player_near_dialogue(_player, node, entered):
|
||||||
print('body entered?', entered)
|
|
||||||
if entered:
|
if entered:
|
||||||
current_dialogue = node.name
|
current_dialogue = node.name
|
||||||
else:
|
else:
|
||||||
current_dialogue = null
|
current_dialogue = null
|
||||||
|
|
||||||
|
func _on_finished():
|
||||||
|
$DialogueCooldown.start()
|
||||||
|
|
||||||
func _process(_delta):
|
func _process(_delta):
|
||||||
if Input.is_action_just_released('ui_accept') and current_dialogue and not $Dialogue/Wol.running:
|
if Input.is_action_just_released('ui_accept') \
|
||||||
print(current_dialogue)
|
and current_dialogue and not $Dialogue/Wol.running and $DialogueCooldown.time_left == 0:
|
||||||
$Dialogue/Wol.starting_node = current_dialogue
|
$Dialogue/Wol.starting_node = current_dialogue
|
||||||
$Dialogue/Wol.path = 'res://ExampleDialogue/%s.yarn' % current_dialogue
|
$Dialogue/Wol.path = 'res://ExampleDialogue/%s.yarn' % current_dialogue
|
||||||
$Dialogue/Wol.start()
|
$Dialogue/Wol.start()
|
||||||
print($Dialogue/Wol.variable_storage)
|
|
||||||
|
|
|
@ -230,3 +230,7 @@ __meta__ = {
|
||||||
|
|
||||||
[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="StaticBody2D"]
|
[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="StaticBody2D"]
|
||||||
polygon = PoolVector2Array( -3, 427, -40, 426, -40, 648, 838, 647, 838, 419, 787, 420, 788, 600, 2, 600, 0, 420 )
|
polygon = PoolVector2Array( -3, 427, -40, 426, -40, 648, 838, 647, 838, 419, 787, 420, 788, 600, 2, 600, 0, 420 )
|
||||||
|
|
||||||
|
[node name="DialogueCooldown" type="Timer" parent="."]
|
||||||
|
wait_time = 0.4
|
||||||
|
one_shot = true
|
||||||
|
|
|
@ -79,7 +79,11 @@ func node_visit_count(node = virtual_machine.current_node.name):
|
||||||
if node is Value:
|
if node is Value:
|
||||||
node = virtual_machine.program.strings[node.value()].text
|
node = virtual_machine.program.strings[node.value()].text
|
||||||
|
|
||||||
|
|
||||||
var variable_storage = virtual_machine.dialogue.variable_storage
|
var variable_storage = virtual_machine.dialogue.variable_storage
|
||||||
var visited_node_count = variable_storage[virtual_machine.program.filename]
|
var visited_node_count = variable_storage[virtual_machine.program.filename]
|
||||||
|
|
||||||
|
print('checking node "%s"' % node)
|
||||||
|
print(variable_storage)
|
||||||
|
|
||||||
return visited_node_count[node] if visited_node_count.has(node) else 0
|
return visited_node_count[node] if visited_node_count.has(node) else 0
|
||||||
|
|
|
@ -68,7 +68,7 @@ func set_node(name):
|
||||||
dialogue.variable_storage[program.filename] = {}
|
dialogue.variable_storage[program.filename] = {}
|
||||||
|
|
||||||
if not dialogue.variable_storage[program.filename].has(name):
|
if not dialogue.variable_storage[program.filename].has(name):
|
||||||
dialogue.variable_storage[program.filename][name] = 0
|
dialogue.variable_storage[program.filename][name] = 1
|
||||||
else:
|
else:
|
||||||
dialogue.variable_storage[program.filename][name] += 1
|
dialogue.variable_storage[program.filename][name] += 1
|
||||||
return true
|
return true
|
||||||
|
@ -287,7 +287,7 @@ func run_instruction(instruction):
|
||||||
var line = option[0]
|
var line = option[0]
|
||||||
var destination = option[1]
|
var destination = option[1]
|
||||||
choices.append(Program.Option.new(line, option_index, destination))
|
choices.append(Program.Option.new(line, option_index, destination))
|
||||||
|
|
||||||
execution_state = Constants.ExecutionState.WaitingForOption
|
execution_state = Constants.ExecutionState.WaitingForOption
|
||||||
options_handler.call_func(choices)
|
options_handler.call_func(choices)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
extends Object
|
extends Object
|
||||||
|
|
||||||
|
signal error(message, line_number, column)
|
||||||
|
|
||||||
const Constants = preload('res://addons/Wol/core/Constants.gd')
|
const Constants = preload('res://addons/Wol/core/Constants.gd')
|
||||||
const Lexer = preload('res://addons/Wol/core/compiler/Lexer.gd')
|
const Lexer = preload('res://addons/Wol/core/compiler/Lexer.gd')
|
||||||
const Program = preload('res://addons/Wol/core/Program.gd')
|
const Program = preload('res://addons/Wol/core/Program.gd')
|
||||||
|
@ -12,13 +14,15 @@ var filename = ''
|
||||||
|
|
||||||
var current_node
|
var current_node
|
||||||
var has_implicit_string_tags = false
|
var has_implicit_string_tags = false
|
||||||
|
var soft_assert = false
|
||||||
|
|
||||||
var string_count = 0
|
var string_count = 0
|
||||||
var string_table = {}
|
var string_table = {}
|
||||||
var label_count = 0
|
var label_count = 0
|
||||||
|
|
||||||
func _init(_filename, _source = null):
|
func _init(_filename, _source = null, _soft_assert = false):
|
||||||
filename = _filename
|
filename = _filename
|
||||||
|
soft_assert = _soft_assert
|
||||||
|
|
||||||
if not _filename and _source:
|
if not _filename and _source:
|
||||||
self.source = _source
|
self.source = _source
|
||||||
|
@ -28,69 +32,119 @@ func _init(_filename, _source = null):
|
||||||
self.source = file.get_as_text()
|
self.source = file.get_as_text()
|
||||||
file.close()
|
file.close()
|
||||||
|
|
||||||
func compile():
|
var source_lines = source.split('\n')
|
||||||
var header_sep = RegEx.new()
|
|
||||||
var header_property = RegEx.new()
|
|
||||||
header_sep.compile('---(\r\n|\r|\n)')
|
|
||||||
header_property.compile('(?<field>.*): *(?<value>.*)')
|
|
||||||
|
|
||||||
assert(header_sep.search(source), 'No headers found!')
|
|
||||||
|
|
||||||
var line_number = 0
|
|
||||||
|
|
||||||
var source_lines = source.split('\n', false)
|
|
||||||
for i in range(source_lines.size()):
|
for i in range(source_lines.size()):
|
||||||
source_lines[i] = source_lines[i].strip_edges(false, true)
|
source_lines[i] = source_lines[i].strip_edges(false, true)
|
||||||
|
|
||||||
var parsed_nodes = []
|
source = source_lines.join('\n')
|
||||||
|
|
||||||
|
func get_headers(offset = 0):
|
||||||
|
var header_property = RegEx.new()
|
||||||
|
var header_sep = RegEx.new()
|
||||||
|
|
||||||
|
header_sep.compile('---(\r\n|\r|\n)')
|
||||||
|
header_property.compile('(?<field>.*): *(?<value>.*)')
|
||||||
|
|
||||||
|
self.assert(header_sep.search(source), 'No headers found!')
|
||||||
|
|
||||||
|
var title = ''
|
||||||
|
var position = Vector2.ZERO
|
||||||
|
|
||||||
|
var source_lines = source.split('\n')
|
||||||
|
var line_number = offset
|
||||||
while line_number < source_lines.size():
|
while line_number < source_lines.size():
|
||||||
var title = ''
|
var line = source_lines[line_number]
|
||||||
var body = ''
|
line_number += 1
|
||||||
|
|
||||||
# Parse header
|
|
||||||
while true:
|
|
||||||
var line = source_lines[line_number]
|
|
||||||
line_number += 1
|
|
||||||
|
|
||||||
if not line.empty():
|
|
||||||
var result = header_property.search(line)
|
|
||||||
if result != null:
|
|
||||||
var field = result.get_string('field')
|
|
||||||
var value = result.get_string('value')
|
|
||||||
|
|
||||||
if field == 'title':
|
|
||||||
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)])
|
|
||||||
|
|
||||||
title = value
|
|
||||||
# TODO: Implement position, color and tags
|
|
||||||
|
|
||||||
if line_number >= source_lines.size() or line == '---':
|
|
||||||
break
|
|
||||||
|
|
||||||
# past header
|
|
||||||
var body_lines = []
|
|
||||||
|
|
||||||
while line_number < source_lines.size() and source_lines[line_number] != '===':
|
if not line.empty():
|
||||||
body_lines.append(source_lines[line_number])
|
var result = header_property.search(line)
|
||||||
line_number += 1
|
|
||||||
|
|
||||||
|
if result != null:
|
||||||
|
var field = result.get_string('field')
|
||||||
|
var value = result.get_string('value')
|
||||||
|
|
||||||
|
if field == 'title':
|
||||||
|
var regex = RegEx.new()
|
||||||
|
regex.compile(INVALID_TITLE)
|
||||||
|
self.assert(not regex.search(value), 'Invalid characters in title "%s", correct to "%s"' % [value, regex.sub(value, '', true)])
|
||||||
|
|
||||||
|
title = value
|
||||||
|
|
||||||
|
if field == 'position':
|
||||||
|
var regex = RegEx.new()
|
||||||
|
regex.compile('^position:.*,.*\\d$')
|
||||||
|
self.assert(regex.search(line), 'Couldn\'t parse position property in the headers, got "%s" instead in node "%s"' % [value, title])
|
||||||
|
|
||||||
|
position = Vector2(int(value.split(',')[0].strip_edges()), int(value.split(',')[1].strip_edges()))
|
||||||
|
|
||||||
|
# TODO: Implement color and tags
|
||||||
|
|
||||||
|
if line == '---':
|
||||||
|
break
|
||||||
|
|
||||||
|
return {
|
||||||
|
'title': title,
|
||||||
|
'position': position
|
||||||
|
}
|
||||||
|
|
||||||
|
func get_body(offset = 0):
|
||||||
|
var body_lines = []
|
||||||
|
|
||||||
|
var source_lines = source.split('\n')
|
||||||
|
var recording = false
|
||||||
|
var line_number = offset
|
||||||
|
|
||||||
|
while line_number < source_lines.size() and source_lines[line_number] != '===':
|
||||||
|
if recording:
|
||||||
|
body_lines.append(source_lines[line_number])
|
||||||
|
|
||||||
|
recording = recording or source_lines[line_number] == '---'
|
||||||
line_number += 1
|
line_number += 1
|
||||||
|
|
||||||
body = PoolStringArray(body_lines).join('\n')
|
line_number += 1
|
||||||
var lexer = Lexer.new(filename, title, body)
|
|
||||||
var tokens = lexer.tokenize()
|
|
||||||
|
|
||||||
var parser = Parser.new(title, tokens)
|
return PoolStringArray(body_lines).join('\n')
|
||||||
var parser_node = parser.parse_node()
|
|
||||||
|
|
||||||
parser_node.name = title
|
func get_nodes():
|
||||||
# parser_node.tags = title
|
var nodes = []
|
||||||
parsed_nodes.append(parser_node)
|
var line_number = 0
|
||||||
|
var source_lines = source.split('\n')
|
||||||
|
while line_number < source_lines.size():
|
||||||
|
var headers = get_headers(line_number)
|
||||||
|
var body = get_body(line_number)
|
||||||
|
headers.body = body
|
||||||
|
|
||||||
|
nodes.append(headers)
|
||||||
|
|
||||||
|
# Add +2 to the final line to skip the === from that node
|
||||||
|
line_number = Array(source_lines).find_last(body.split('\n')[-1]) + 2
|
||||||
|
|
||||||
while line_number < source_lines.size() and source_lines[line_number].empty():
|
while line_number < source_lines.size() and source_lines[line_number].empty():
|
||||||
line_number += 1
|
line_number += 1
|
||||||
|
|
||||||
|
return nodes
|
||||||
|
|
||||||
|
func assert(statement, message, line_number = -1, column = -1, _absolute_line_number = -1):
|
||||||
|
if not soft_assert:
|
||||||
|
assert(statement, message + ('; on line %d column %d' % [line_number, column]))
|
||||||
|
elif not statement:
|
||||||
|
emit_signal('error', message, line_number, column)
|
||||||
|
|
||||||
|
func compile():
|
||||||
|
var parsed_nodes = []
|
||||||
|
for node in get_nodes():
|
||||||
|
var lexer = Lexer.new(self, filename, node.title, node.body)
|
||||||
|
var tokens = lexer.tokenize()
|
||||||
|
|
||||||
|
# In case of lexer error
|
||||||
|
if not tokens:
|
||||||
|
return
|
||||||
|
|
||||||
|
var parser = Parser.new(self, node.title, tokens)
|
||||||
|
var parser_node = parser.parse_node()
|
||||||
|
|
||||||
|
parser_node.name = node.title
|
||||||
|
parsed_nodes.append(parser_node)
|
||||||
|
|
||||||
var program = Program.new()
|
var program = Program.new()
|
||||||
program.filename = filename
|
program.filename = filename
|
||||||
|
@ -104,7 +158,7 @@ func compile():
|
||||||
return program
|
return program
|
||||||
|
|
||||||
func compile_node(program, parsed_node):
|
func compile_node(program, parsed_node):
|
||||||
assert(not program.nodes.has(parsed_node.name), 'Duplicate node in program: %s' % parsed_node.name)
|
self.assert(not program.nodes.has(parsed_node.name), 'Duplicate node in program: %s' % parsed_node.name)
|
||||||
|
|
||||||
var node_compiled = Program.WolNode.new()
|
var node_compiled = Program.WolNode.new()
|
||||||
|
|
||||||
|
@ -205,7 +259,7 @@ func generate_statement(node, statement):
|
||||||
Constants.StatementTypes.Line:
|
Constants.StatementTypes.Line:
|
||||||
generate_line(node, statement)
|
generate_line(node, statement)
|
||||||
_:
|
_:
|
||||||
assert(false, 'Illegal statement type [%s]. Could not generate code.' % statement.type)
|
self.assert(false, statement.line_number, 'Illegal statement type [%s]. Could not generate code.' % statement.type)
|
||||||
|
|
||||||
func generate_custom_command(node, command):
|
func generate_custom_command(node, command):
|
||||||
# TODO: See if the first tree of this statement is being used
|
# TODO: See if the first tree of this statement is being used
|
||||||
|
@ -234,7 +288,7 @@ func generate_shortcut_group(node, shortcut_group):
|
||||||
var end = register_label('group_end')
|
var end = register_label('group_end')
|
||||||
var labels = []
|
var labels = []
|
||||||
var option_count = 0
|
var option_count = 0
|
||||||
|
|
||||||
for option in shortcut_group.options:
|
for option in shortcut_group.options:
|
||||||
var endof_clause = ''
|
var endof_clause = ''
|
||||||
var op_destination = register_label('option_%s' % [option_count + 1])
|
var op_destination = register_label('option_%s' % [option_count + 1])
|
||||||
|
|
|
@ -2,7 +2,7 @@ extends Object
|
||||||
|
|
||||||
const Constants = preload('res://addons/Wol/core/Constants.gd')
|
const Constants = preload('res://addons/Wol/core/Constants.gd')
|
||||||
|
|
||||||
const LINE_COMENT = '//'
|
const LINE_COMMENT = '//'
|
||||||
const FORWARD_SLASH = '/'
|
const FORWARD_SLASH = '/'
|
||||||
const LINE_SEPARATOR = '\n'
|
const LINE_SEPARATOR = '\n'
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ const FORMAT_FUNCTION = 'format'
|
||||||
|
|
||||||
var WHITESPACE = '\\s*'
|
var WHITESPACE = '\\s*'
|
||||||
|
|
||||||
|
var compiler
|
||||||
var filename = ''
|
var filename = ''
|
||||||
var title = ''
|
var title = ''
|
||||||
var text = ''
|
var text = ''
|
||||||
|
@ -33,14 +34,15 @@ var current_state
|
||||||
var indent_stack = []
|
var indent_stack = []
|
||||||
var should_track_indent = false
|
var should_track_indent = false
|
||||||
|
|
||||||
func _init(_filename, _title, _text):
|
func _init(_compiler, _filename, _title, _text):
|
||||||
createstates()
|
create_states()
|
||||||
|
|
||||||
|
compiler = _compiler
|
||||||
filename = _filename
|
filename = _filename
|
||||||
title = _title
|
title = _title
|
||||||
text = _text
|
text = _text
|
||||||
|
|
||||||
func createstates():
|
func create_states():
|
||||||
var patterns = {}
|
var patterns = {}
|
||||||
patterns[Constants.TokenType.Text] = ['.*', 'any text']
|
patterns[Constants.TokenType.Text] = ['.*', 'any text']
|
||||||
|
|
||||||
|
@ -111,8 +113,8 @@ func createstates():
|
||||||
states[BASE].add_transition(Constants.TokenType.TagMarker, TAG, true)
|
states[BASE].add_transition(Constants.TokenType.TagMarker, TAG, true)
|
||||||
states[BASE].add_text_rule(Constants.TokenType.Text)
|
states[BASE].add_text_rule(Constants.TokenType.Text)
|
||||||
|
|
||||||
#TODO: FIXME - Tags are not being proccessed properly this way. We must look for the format #{identifier}:{value}
|
#FIXME - Tags are not being proccessed properly this way. We must look for the format #{identifier}:{value}
|
||||||
# Possible solution is to add more transitions
|
# Possible solution is to add more transitions
|
||||||
states[TAG] = LexerState.new(patterns)
|
states[TAG] = LexerState.new(patterns)
|
||||||
states[TAG].add_transition(Constants.TokenType.Identifier, BASE)
|
states[TAG].add_transition(Constants.TokenType.Identifier, BASE)
|
||||||
|
|
||||||
|
@ -236,7 +238,11 @@ func tokenize():
|
||||||
lines.append('')
|
lines.append('')
|
||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
tokens += tokenize_line(line, line_number)
|
var line_tokens = tokenize_line(line, line_number)
|
||||||
|
if line_tokens == null:
|
||||||
|
return
|
||||||
|
|
||||||
|
tokens.append_array(line_tokens)
|
||||||
line_number += 1
|
line_number += 1
|
||||||
|
|
||||||
var end_of_input = Token.new(
|
var end_of_input = Token.new(
|
||||||
|
@ -252,12 +258,12 @@ func tokenize():
|
||||||
func tokenize_line(line, line_number):
|
func tokenize_line(line, line_number):
|
||||||
var token_stack = []
|
var token_stack = []
|
||||||
|
|
||||||
var fresh_line = line.replace('\t',' ').replace('\r','')
|
var fresh_line = line.replace('\t', ' ').replace('\r', '')
|
||||||
|
|
||||||
var indentation = line_indentation(line)
|
var indentation = line_indentation(line)
|
||||||
var previous_indentation = indent_stack.front()[0]
|
var previous_indentation = indent_stack.front()[0]
|
||||||
|
|
||||||
if should_track_indent && indentation > previous_indentation:
|
if should_track_indent and indentation > previous_indentation:
|
||||||
indent_stack.push_front([indentation, true])
|
indent_stack.push_front([indentation, true])
|
||||||
|
|
||||||
var indent = Token.new(
|
var indent = Token.new(
|
||||||
|
@ -284,7 +290,7 @@ func tokenize_line(line, line_number):
|
||||||
whitespace.compile(WHITESPACE)
|
whitespace.compile(WHITESPACE)
|
||||||
|
|
||||||
while column < fresh_line.length():
|
while column < fresh_line.length():
|
||||||
if fresh_line.substr(column).begins_with(LINE_COMENT):
|
if fresh_line.substr(column).begins_with(LINE_COMMENT):
|
||||||
break
|
break
|
||||||
|
|
||||||
var matched = false
|
var matched = false
|
||||||
|
@ -367,12 +373,12 @@ func tokenize_line(line, line_number):
|
||||||
line_number,
|
line_number,
|
||||||
column
|
column
|
||||||
]
|
]
|
||||||
assert(false, 'Expected %s in file %s in node "%s" on line #%d (column #%d)' % error_data)
|
compiler.assert(false, 'Expected %s in file %s in node "%s" on line #%d (column #%d)' % error_data, line_number, column)
|
||||||
|
return
|
||||||
|
|
||||||
var last_whitespace = whitespace.search(line, column)
|
var last_whitespace = whitespace.search(line, column)
|
||||||
if last_whitespace:
|
if last_whitespace:
|
||||||
column += last_whitespace.get_string().length()
|
column += last_whitespace.get_string().length()
|
||||||
|
|
||||||
|
|
||||||
token_stack.invert()
|
token_stack.invert()
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
extends Object
|
extends Object
|
||||||
|
|
||||||
|
# warnings-disable
|
||||||
|
|
||||||
const Constants = preload('res://addons/Wol/core/Constants.gd')
|
const Constants = preload('res://addons/Wol/core/Constants.gd')
|
||||||
const Lexer = preload('res://addons/Wol/core/compiler/Lexer.gd')
|
const Lexer = preload('res://addons/Wol/core/compiler/Lexer.gd')
|
||||||
const Value = preload('res://addons/Wol/core/Value.gd')
|
const Value = preload('res://addons/Wol/core/Value.gd')
|
||||||
|
|
||||||
var tokens = []
|
var compiler
|
||||||
var title = ''
|
var title = ''
|
||||||
|
var tokens = []
|
||||||
|
|
||||||
func _init(_title, _tokens):
|
func _init(_compiler, _title, _tokens):
|
||||||
|
compiler = _compiler
|
||||||
title = _title
|
title = _title
|
||||||
tokens = _tokens
|
tokens = _tokens
|
||||||
|
|
||||||
|
@ -21,13 +25,9 @@ func parse_node():
|
||||||
return WolNode.new('Start', null, self)
|
return WolNode.new('Start', null, self)
|
||||||
|
|
||||||
func next_symbol_is(valid_types):
|
func next_symbol_is(valid_types):
|
||||||
var type = self.tokens.front().type
|
return tokens.front().type in valid_types
|
||||||
for valid_type in valid_types:
|
|
||||||
if type == valid_type:
|
|
||||||
return true
|
|
||||||
return false
|
|
||||||
|
|
||||||
# NOTE:0 look ahead for `<<` and `else`
|
# NOTE: 0 look ahead for `<<` and `else`
|
||||||
func next_symbols_are(valid_types):
|
func next_symbols_are(valid_types):
|
||||||
var temporary = [] + tokens
|
var temporary = [] + tokens
|
||||||
for type in valid_types:
|
for type in valid_types:
|
||||||
|
@ -40,7 +40,7 @@ func expect_symbol(token_types = []):
|
||||||
|
|
||||||
if token_types.size() == 0:
|
if token_types.size() == 0:
|
||||||
if token.type == Constants.TokenType.EndOfInput:
|
if token.type == Constants.TokenType.EndOfInput:
|
||||||
assert(false, 'Unexpected end of input')
|
compiler.assert(false, 'Unexpected end of input')
|
||||||
return token
|
return token
|
||||||
|
|
||||||
for type in token_types:
|
for type in token_types:
|
||||||
|
@ -60,15 +60,12 @@ func expect_symbol(token_types = []):
|
||||||
error_guess = ''
|
error_guess = ''
|
||||||
|
|
||||||
var error_data = [
|
var error_data = [
|
||||||
token.filename,
|
|
||||||
title,
|
|
||||||
token.line_number,
|
|
||||||
token.column,
|
|
||||||
PoolStringArray(token_names).join(', '),
|
PoolStringArray(token_names).join(', '),
|
||||||
Constants.token_type_name(token.type),
|
Constants.token_type_name(token.type),
|
||||||
error_guess
|
error_guess
|
||||||
]
|
]
|
||||||
assert(false, '[%s|%s:%d:%d]:\nExpected token "%s" but got "%s"%s' % error_data)
|
compiler.assert(false, 'Expected token "%s" but got "%s"%s' % error_data, token.line_number, token.column)
|
||||||
|
return token
|
||||||
|
|
||||||
static func tab(indent_level, input, newline = true):
|
static func tab(indent_level, input, newline = true):
|
||||||
return '%*s| %s%s' % [indent_level * 2, '', input, '' if not newline else '\n']
|
return '%*s| %s%s' % [indent_level * 2, '', input, '' if not newline else '\n']
|
||||||
|
@ -111,11 +108,21 @@ class WolNode extends ParseNode:
|
||||||
|
|
||||||
var editor_node_tags = []
|
var editor_node_tags = []
|
||||||
var statements = []
|
var statements = []
|
||||||
|
var parser
|
||||||
|
|
||||||
func _init(name, parent, parser).(parent, parser):
|
func _init(_name, parent, _parser).(parent, _parser):
|
||||||
self.name = name
|
name = _name
|
||||||
|
parser = _parser
|
||||||
while parser.tokens.size() > 0 \
|
while parser.tokens.size() > 0 \
|
||||||
and not parser.next_symbol_is([Constants.TokenType.Dedent, Constants.TokenType.EndOfInput]):
|
and not parser.next_symbol_is([Constants.TokenType.Dedent, Constants.TokenType.EndOfInput]):
|
||||||
|
|
||||||
|
parser.compiler.assert(
|
||||||
|
not parser.next_symbol_is([Constants.TokenType.Indent]),
|
||||||
|
'Found a stray indentation!',
|
||||||
|
parser.tokens.front().line_number,
|
||||||
|
parser.tokens.front().column
|
||||||
|
)
|
||||||
|
|
||||||
statements.append(Statement.new(self, parser))
|
statements.append(Statement.new(self, parser))
|
||||||
|
|
||||||
func tree_string(indent_level):
|
func tree_string(indent_level):
|
||||||
|
@ -125,109 +132,6 @@ class WolNode extends ParseNode:
|
||||||
|
|
||||||
return PoolStringArray(info).join('')
|
return PoolStringArray(info).join('')
|
||||||
|
|
||||||
# TODO: Evaluate use
|
|
||||||
class Header extends ParseNode:
|
|
||||||
pass
|
|
||||||
|
|
||||||
class InlineExpression extends ParseNode:
|
|
||||||
var expression
|
|
||||||
|
|
||||||
func _init(parent, parser).(parent, parser):
|
|
||||||
parser.expect_symbol([Constants.TokenType.ExpressionFunctionStart])
|
|
||||||
expression = ExpressionNode.parse(self, parser)
|
|
||||||
parser.expect_symbol([Constants.TokenType.ExpressionFunctionEnd])
|
|
||||||
|
|
||||||
static func can_parse(parser):
|
|
||||||
return parser.next_symbol_is([Constants.TokenType.ExpressionFunctionStart])
|
|
||||||
|
|
||||||
func tree_string(_indent_level):
|
|
||||||
return "InlineExpression:"
|
|
||||||
|
|
||||||
# Returns a format_text string as [ name "{0}" key1="value1" key2="value2" ]
|
|
||||||
class FormatFunctionNode extends ParseNode:
|
|
||||||
var format_text = ''
|
|
||||||
var expression_value
|
|
||||||
|
|
||||||
func _init(parent:ParseNode, parser, expressionCount:int).(parent, parser):
|
|
||||||
format_text="["
|
|
||||||
parser.expect_symbol([Constants.TokenType.FormatFunctionStart])
|
|
||||||
|
|
||||||
while not parser.next_symbol_is([Constants.TokenType.FormatFunctionEnd]):
|
|
||||||
if parser.next_symbol_is([Constants.TokenType.Text]):
|
|
||||||
format_text += parser.expect_symbol().value
|
|
||||||
|
|
||||||
if InlineExpression.can_parse(parser):
|
|
||||||
expression_value = InlineExpression.new(self, parser)
|
|
||||||
format_text +=" \"{%d}\" " % expressionCount
|
|
||||||
parser.expect_symbol()
|
|
||||||
format_text+="]"
|
|
||||||
|
|
||||||
static func can_parse(parser):
|
|
||||||
return parser.next_symbol_is([Constants.TokenType.FormatFunctionStart])
|
|
||||||
|
|
||||||
# TODO: Make format prettier and add more information
|
|
||||||
func tree_string(_indent_level):
|
|
||||||
return "FormatFunction"
|
|
||||||
|
|
||||||
class LineNode extends ParseNode:
|
|
||||||
var line_text = ''
|
|
||||||
# FIXME: Right now we are putting the formatfunctions and inline expressions in the same
|
|
||||||
# list but if at some point we want to stronly type our sub list we need to make a new
|
|
||||||
# parse node that can have either an InlineExpression or a FunctionFormat
|
|
||||||
# .. This is a consideration for Godot4.x
|
|
||||||
var substitutions = []
|
|
||||||
var line_id = ''
|
|
||||||
var line_tags = []
|
|
||||||
|
|
||||||
# NOTE: If format function an inline functions are both present
|
|
||||||
# returns a line in the format "Some text {0} and some other {1}[format "{2}" key="value" key="value"]"
|
|
||||||
|
|
||||||
func _init(parent, parser).(parent, parser):
|
|
||||||
while parser.next_symbol_is(
|
|
||||||
[
|
|
||||||
Constants.TokenType.FormatFunctionStart,
|
|
||||||
Constants.TokenType.ExpressionFunctionStart,
|
|
||||||
Constants.TokenType.Text,
|
|
||||||
Constants.TokenType.TagMarker
|
|
||||||
]
|
|
||||||
):
|
|
||||||
|
|
||||||
if FormatFunctionNode.can_parse(parser):
|
|
||||||
var format_function = FormatFunctionNode.new(self, parser, substitutions.size())
|
|
||||||
if format_function.expression_value != null:
|
|
||||||
substitutions.append(format_function.expression_value)
|
|
||||||
|
|
||||||
line_text += format_function.format_text
|
|
||||||
|
|
||||||
elif InlineExpression.can_parse(parser):
|
|
||||||
var inline_expression = InlineExpression.new(self, parser)
|
|
||||||
line_text += '{%d}' % substitutions.size()
|
|
||||||
substitutions.append(inline_expression)
|
|
||||||
|
|
||||||
elif parser.next_symbols_are([Constants.TokenType.TagMarker, Constants.TokenType.Identifier]):
|
|
||||||
parser.expect_symbol()
|
|
||||||
var tag_token = parser.expect_symbol([ Constants.TokenType.Identifier ])
|
|
||||||
if tag_token.value.begins_with("line:"):
|
|
||||||
if line_id.empty():
|
|
||||||
line_id = tag_token.value
|
|
||||||
else:
|
|
||||||
printerr("Too many line_tags @[%s:%d]" %[parser.currentNodeName, tag_token.line_number])
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
tags.append(tag_token.value)
|
|
||||||
|
|
||||||
else:
|
|
||||||
var token = parser.expect_symbol()
|
|
||||||
if token.line_number == line_number and token.type != Constants.TokenType.BeginCommand:
|
|
||||||
line_text += token.value
|
|
||||||
else:
|
|
||||||
parser.tokens.push_front(token)
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
func tree_string(indent_level):
|
|
||||||
return tab(indent_level, 'Line: (%s)[%d]' % [line_text, substitutions.size()])
|
|
||||||
|
|
||||||
class Statement extends ParseNode:
|
class Statement extends ParseNode:
|
||||||
var Type = Constants.StatementTypes
|
var Type = Constants.StatementTypes
|
||||||
|
|
||||||
|
@ -270,7 +174,7 @@ class Statement extends ParseNode:
|
||||||
type = Type.Line
|
type = Type.Line
|
||||||
|
|
||||||
else:
|
else:
|
||||||
printerr('Expected a statement but got %s instead. (probably an imbalanced if statement)' % parser.tokens.front()._to_string())
|
parser.compiler.assert(false, 'Expected a statement but got %s instead. (probably an imbalanced if statement)' % parser.tokens.front()._to_string())
|
||||||
|
|
||||||
var tags = []
|
var tags = []
|
||||||
|
|
||||||
|
@ -305,6 +209,107 @@ class Statement extends ParseNode:
|
||||||
|
|
||||||
return PoolStringArray(info).join('')
|
return PoolStringArray(info).join('')
|
||||||
|
|
||||||
|
class InlineExpression extends ParseNode:
|
||||||
|
var expression
|
||||||
|
|
||||||
|
func _init(parent, parser).(parent, parser):
|
||||||
|
parser.expect_symbol([Constants.TokenType.ExpressionFunctionStart])
|
||||||
|
expression = ExpressionNode.parse(self, parser)
|
||||||
|
parser.expect_symbol([Constants.TokenType.ExpressionFunctionEnd])
|
||||||
|
|
||||||
|
static func can_parse(parser):
|
||||||
|
return parser.next_symbol_is([Constants.TokenType.ExpressionFunctionStart])
|
||||||
|
|
||||||
|
func tree_string(_indent_level):
|
||||||
|
return "InlineExpression:"
|
||||||
|
|
||||||
|
# Returns a format_text string as [ name "{0}" key1="value1" key2="value2" ]
|
||||||
|
class FormatFunctionNode extends ParseNode:
|
||||||
|
var format_text = ''
|
||||||
|
var expression_value
|
||||||
|
|
||||||
|
func _init(parent:ParseNode, parser, expressionCount:int).(parent, parser):
|
||||||
|
format_text="["
|
||||||
|
parser.expect_symbol([Constants.TokenType.FormatFunctionStart])
|
||||||
|
|
||||||
|
# FIXME: Add exit condition in case of failure
|
||||||
|
while not parser.next_symbol_is([Constants.TokenType.FormatFunctionEnd]):
|
||||||
|
if parser.next_symbol_is([Constants.TokenType.Text]):
|
||||||
|
format_text += parser.expect_symbol().value
|
||||||
|
|
||||||
|
if InlineExpression.can_parse(parser):
|
||||||
|
expression_value = InlineExpression.new(self, parser)
|
||||||
|
format_text +=" \"{%d}\" " % expressionCount
|
||||||
|
parser.expect_symbol()
|
||||||
|
format_text+="]"
|
||||||
|
|
||||||
|
static func can_parse(parser):
|
||||||
|
return parser.next_symbol_is([Constants.TokenType.FormatFunctionStart])
|
||||||
|
|
||||||
|
# TODO: Make format prettier and add more information
|
||||||
|
func tree_string(_indent_level):
|
||||||
|
return "FormatFunction"
|
||||||
|
|
||||||
|
class LineNode extends ParseNode:
|
||||||
|
var line_text = ''
|
||||||
|
# FIXME: Right now we are putting the formatfunctions and inline expressions in the same
|
||||||
|
# list but if at some point we want to strongly type our sub list we need to make a new
|
||||||
|
# parse node that can have either an InlineExpression or a FunctionFormat
|
||||||
|
# .. This is a consideration for Godot4.x
|
||||||
|
var substitutions = []
|
||||||
|
var line_id = ''
|
||||||
|
var line_tags = []
|
||||||
|
|
||||||
|
# NOTE: If format function an inline functions are both present
|
||||||
|
# returns a line in the format "Some text {0} and some other {1}[format "{2}" key="value" key="value"]"
|
||||||
|
|
||||||
|
func _init(parent, parser).(parent, parser):
|
||||||
|
while parser.next_symbol_is(
|
||||||
|
[
|
||||||
|
Constants.TokenType.FormatFunctionStart,
|
||||||
|
Constants.TokenType.ExpressionFunctionStart,
|
||||||
|
Constants.TokenType.Text,
|
||||||
|
Constants.TokenType.TagMarker
|
||||||
|
]
|
||||||
|
):
|
||||||
|
|
||||||
|
if FormatFunctionNode.can_parse(parser):
|
||||||
|
var format_function = FormatFunctionNode.new(self, parser, substitutions.size())
|
||||||
|
if format_function.expression_value != null:
|
||||||
|
substitutions.append(format_function.expression_value)
|
||||||
|
|
||||||
|
line_text += format_function.format_text
|
||||||
|
|
||||||
|
elif InlineExpression.can_parse(parser):
|
||||||
|
var inline_expression = InlineExpression.new(self, parser)
|
||||||
|
line_text += '{%d}' % substitutions.size()
|
||||||
|
substitutions.append(inline_expression)
|
||||||
|
|
||||||
|
elif parser.next_symbols_are([Constants.TokenType.TagMarker, Constants.TokenType.Identifier]):
|
||||||
|
parser.expect_symbol()
|
||||||
|
var tag_token = parser.expect_symbol([ Constants.TokenType.Identifier ])
|
||||||
|
if tag_token.value.begins_with("line:"):
|
||||||
|
if line_id.empty():
|
||||||
|
line_id = tag_token.value
|
||||||
|
else:
|
||||||
|
printerr("Too many line_tags @[%s:%d]" % [parser.currentNodeName, tag_token.line_number])
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
tags.append(tag_token.value)
|
||||||
|
|
||||||
|
else:
|
||||||
|
var token = parser.expect_symbol()
|
||||||
|
if token.line_number == line_number and token.type != Constants.TokenType.BeginCommand:
|
||||||
|
line_text += token.value
|
||||||
|
else:
|
||||||
|
parser.tokens.push_front(token)
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
func tree_string(indent_level):
|
||||||
|
return tab(indent_level, 'Line: (%s)[%d]' % [line_text, substitutions.size()])
|
||||||
|
|
||||||
|
|
||||||
class CustomCommand extends ParseNode:
|
class CustomCommand extends ParseNode:
|
||||||
|
|
||||||
enum Type {
|
enum Type {
|
||||||
|
@ -322,6 +327,7 @@ class CustomCommand extends ParseNode:
|
||||||
var command_tokens = []
|
var command_tokens = []
|
||||||
command_tokens.append(parser.expect_symbol())
|
command_tokens.append(parser.expect_symbol())
|
||||||
|
|
||||||
|
# FIXME: add exit condition
|
||||||
while not parser.next_symbol_is([Constants.TokenType.EndCommand]):
|
while not parser.next_symbol_is([Constants.TokenType.EndCommand]):
|
||||||
command_tokens.append(parser.expect_symbol())
|
command_tokens.append(parser.expect_symbol())
|
||||||
|
|
||||||
|
@ -338,9 +344,9 @@ class CustomCommand extends ParseNode:
|
||||||
type = Type.Expression
|
type = Type.Expression
|
||||||
|
|
||||||
else:
|
else:
|
||||||
#otherwise evaluuate command
|
# otherwise evaluate command
|
||||||
type = Type.ClientCommand
|
type = Type.ClientCommand
|
||||||
self.client_command = command_tokens[0].value
|
client_command = command_tokens[0].value
|
||||||
|
|
||||||
func tree_string(indent_level):
|
func tree_string(indent_level):
|
||||||
match type:
|
match type:
|
||||||
|
@ -362,9 +368,7 @@ class ShortcutOptionGroup extends ParseNode:
|
||||||
# parse options until there is no more
|
# parse options until there is no more
|
||||||
# expect one otherwise invalid
|
# expect one otherwise invalid
|
||||||
|
|
||||||
var index = 1
|
var index = 0
|
||||||
options.append(ShortCutOption.new(index, self, parser))
|
|
||||||
index += 1
|
|
||||||
while parser.next_symbol_is([Constants.TokenType.ShortcutOption]):
|
while parser.next_symbol_is([Constants.TokenType.ShortcutOption]):
|
||||||
options.append(ShortCutOption.new(index, self, parser))
|
options.append(ShortCutOption.new(index, self, parser))
|
||||||
index += 1
|
index += 1
|
||||||
|
@ -393,7 +397,7 @@ class ShortCutOption extends ParseNode:
|
||||||
parser.expect_symbol([Constants.TokenType.ShortcutOption])
|
parser.expect_symbol([Constants.TokenType.ShortcutOption])
|
||||||
label = parser.expect_symbol([Constants.TokenType.Text]).value
|
label = parser.expect_symbol([Constants.TokenType.Text]).value
|
||||||
|
|
||||||
# NOTE: Parse the conditional << if $x >> when it exists
|
# FIXME: Parse the conditional << if $x >> when it exists
|
||||||
var tags = []
|
var tags = []
|
||||||
while parser.next_symbols_are([Constants.TokenType.BeginCommand, Constants.TokenType.IfToken]) \
|
while parser.next_symbols_are([Constants.TokenType.BeginCommand, Constants.TokenType.IfToken]) \
|
||||||
or parser.next_symbol_is([Constants.TokenType.TagMarker]):
|
or parser.next_symbol_is([Constants.TokenType.TagMarker]):
|
||||||
|
@ -446,6 +450,7 @@ class Block extends ParseNode:
|
||||||
parser.expect_symbol([Constants.TokenType.Indent])
|
parser.expect_symbol([Constants.TokenType.Indent])
|
||||||
|
|
||||||
#keep reading statements until we hit a dedent
|
#keep reading statements until we hit a dedent
|
||||||
|
# FIXME: find exit condition
|
||||||
while not parser.next_symbol_is([Constants.TokenType.Dedent]):
|
while not parser.next_symbol_is([Constants.TokenType.Dedent]):
|
||||||
#parse all statements including nested blocks
|
#parse all statements including nested blocks
|
||||||
statements.append(Statement.new(self, parser))
|
statements.append(Statement.new(self, parser))
|
||||||
|
@ -669,6 +674,8 @@ class ExpressionNode extends ParseNode:
|
||||||
|
|
||||||
# Using Djikstra's shunting-yard algorithm to convert stream of expresions into postfix notation,
|
# Using Djikstra's shunting-yard algorithm to convert stream of expresions into postfix notation,
|
||||||
# & then build a tree of expressions
|
# & then build a tree of expressions
|
||||||
|
|
||||||
|
# TODO: Rework expression parsing
|
||||||
static func parse(parent, parser):
|
static func parse(parent, parser):
|
||||||
var rpn = []
|
var rpn = []
|
||||||
var op_stack = []
|
var op_stack = []
|
||||||
|
@ -693,20 +700,30 @@ class ExpressionNode extends ParseNode:
|
||||||
|
|
||||||
var last
|
var last
|
||||||
|
|
||||||
|
print(parser.tokens.slice(0, 6))
|
||||||
#read expression content
|
#read expression content
|
||||||
while parser.tokens.size() > 0 and parser.next_symbol_is(valid_types):
|
while parser.tokens.size() > 0 and parser.next_symbol_is(valid_types):
|
||||||
|
print(parser.tokens.front())
|
||||||
var next = parser.expect_symbol(valid_types)
|
var next = parser.expect_symbol(valid_types)
|
||||||
|
|
||||||
if next.type == Constants.TokenType.Variable \
|
if next.type in [
|
||||||
or next.type == Constants.TokenType.Number \
|
Constants.TokenType.Variable,
|
||||||
or next.type == Constants.TokenType.Str \
|
Constants.TokenType.Number,
|
||||||
or next.type == Constants.TokenType.FalseToken \
|
Constants.TokenType.Str,
|
||||||
or next.type == Constants.TokenType.TrueToken \
|
Constants.TokenType.FalseToken,
|
||||||
or next.type == Constants.TokenType.NullToken:
|
Constants.TokenType.TrueToken,
|
||||||
|
Constants.TokenType.NullToken
|
||||||
#output primitives
|
]:
|
||||||
rpn.append(next)
|
|
||||||
|
# Output primitives
|
||||||
|
print('adding value "%s" in expression' % next)
|
||||||
|
print(op_stack.size())
|
||||||
|
if func_stack.size() != 0:
|
||||||
|
op_stack.append(next)
|
||||||
|
else:
|
||||||
|
rpn.append(next)
|
||||||
elif next.type == Constants.TokenType.Identifier:
|
elif next.type == Constants.TokenType.Identifier:
|
||||||
|
print('adding function')
|
||||||
op_stack.push_back(next)
|
op_stack.push_back(next)
|
||||||
func_stack.push_back(next)
|
func_stack.push_back(next)
|
||||||
|
|
||||||
|
@ -719,7 +736,7 @@ class ExpressionNode extends ParseNode:
|
||||||
while op_stack.back().type != Constants.TokenType.LeftParen:
|
while op_stack.back().type != Constants.TokenType.LeftParen:
|
||||||
var p = op_stack.pop_back()
|
var p = op_stack.pop_back()
|
||||||
if p == null:
|
if p == null:
|
||||||
printerr('unbalanced parenthesis %s ' % next.name)
|
printerr('unbalanced parenthesis %s' % next.name)
|
||||||
break
|
break
|
||||||
rpn.append(p)
|
rpn.append(p)
|
||||||
|
|
||||||
|
@ -732,7 +749,7 @@ class ExpressionNode extends ParseNode:
|
||||||
|
|
||||||
#find the closest function on stack
|
#find the closest function on stack
|
||||||
#increment parameters
|
#increment parameters
|
||||||
func_stack.back().parameter_count+=1
|
func_stack.back().parameter_count += 1
|
||||||
|
|
||||||
elif Operator.is_op(next.type):
|
elif Operator.is_op(next.type):
|
||||||
#this is an operator
|
#this is an operator
|
||||||
|
@ -759,7 +776,7 @@ class ExpressionNode extends ParseNode:
|
||||||
next.type = Constants.TokenType.EqualTo
|
next.type = Constants.TokenType.EqualTo
|
||||||
|
|
||||||
#operator precedence
|
#operator precedence
|
||||||
while (ExpressionNode.is_apply_precedence(next.type, op_stack)):
|
while ExpressionNode.is_apply_precedence(next.type, op_stack, parser):
|
||||||
var op = op_stack.pop_back()
|
var op = op_stack.pop_back()
|
||||||
rpn.append(op)
|
rpn.append(op)
|
||||||
|
|
||||||
|
@ -771,20 +788,28 @@ class ExpressionNode extends ParseNode:
|
||||||
elif next.type == Constants.TokenType.RightParen:
|
elif next.type == Constants.TokenType.RightParen:
|
||||||
#leaving sub expression
|
#leaving sub expression
|
||||||
# resolve order of operations
|
# resolve order of operations
|
||||||
|
var parameters = []
|
||||||
while op_stack.back().type != Constants.TokenType.LeftParen:
|
while op_stack.back().type != Constants.TokenType.LeftParen:
|
||||||
rpn.append(op_stack.pop_back())
|
parameters.append(op_stack.pop_back())
|
||||||
if op_stack.back() == null:
|
|
||||||
printerr('Unbalanced parenthasis #RightParen. Parser.ExpressionNode')
|
parser.compiler.assert(
|
||||||
|
op_stack.back() != null,
|
||||||
|
'Unbalanced parenthasis #RightParen. Parser.ExpressionNode'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
rpn.append_array(parameters)
|
||||||
op_stack.pop_back()
|
op_stack.pop_back()
|
||||||
|
# FIXME: Something is going on with parameter counting, fixed for now
|
||||||
|
# but needs a bigger rework
|
||||||
if op_stack.back().type == Constants.TokenType.Identifier:
|
if op_stack.back().type == Constants.TokenType.Identifier:
|
||||||
#function call
|
#function call
|
||||||
#last token == left paren this == no parameters
|
#last token == left paren this == no parameters
|
||||||
#else
|
#else
|
||||||
#we have more than 1 param
|
#we have more than 1 param
|
||||||
if last.type != Constants.TokenType.LeftParen:
|
# if last.type != Constants.TokenType.LeftParen:
|
||||||
func_stack.back().parameter_count+=1
|
# func_stack.back().parameter_count += 1
|
||||||
|
func_stack.back().parameter_count = parameters.size()
|
||||||
|
|
||||||
rpn.append(op_stack.pop_back())
|
rpn.append(op_stack.pop_back())
|
||||||
func_stack.pop_back()
|
func_stack.pop_back()
|
||||||
|
@ -804,6 +829,8 @@ class ExpressionNode extends ParseNode:
|
||||||
var first = rpn.front()
|
var first = rpn.front()
|
||||||
var eval_stack = []#ExpressionNode
|
var eval_stack = []#ExpressionNode
|
||||||
|
|
||||||
|
print(rpn)
|
||||||
|
|
||||||
while rpn.size() > 0:
|
while rpn.size() > 0:
|
||||||
var next = rpn.pop_front()
|
var next = rpn.pop_front()
|
||||||
if Operator.is_op(next.type):
|
if Operator.is_op(next.type):
|
||||||
|
@ -811,7 +838,14 @@ class ExpressionNode extends ParseNode:
|
||||||
var info = Operator.op_info(next.type)
|
var info = Operator.op_info(next.type)
|
||||||
|
|
||||||
if eval_stack.size() < info.arguments:
|
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])
|
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 function_parameters = []
|
var function_parameters = []
|
||||||
for _i in range(info.arguments):
|
for _i in range(info.arguments):
|
||||||
|
@ -827,6 +861,7 @@ class ExpressionNode extends ParseNode:
|
||||||
# A function call
|
# A function call
|
||||||
elif next.type == Constants.TokenType.Identifier:
|
elif next.type == Constants.TokenType.Identifier:
|
||||||
var function_name = next.value
|
var function_name = next.value
|
||||||
|
prints(function_name, next.parameter_count)
|
||||||
|
|
||||||
var function_parameters = []
|
var function_parameters = []
|
||||||
for _i in range(next.parameter_count):
|
for _i in range(next.parameter_count):
|
||||||
|
@ -845,10 +880,14 @@ class ExpressionNode extends ParseNode:
|
||||||
eval_stack.append(expression)
|
eval_stack.append(expression)
|
||||||
|
|
||||||
|
|
||||||
#we should have a single root expression left
|
# NOTE: We should have a single root expression left
|
||||||
#if more then we failed ---- NANI
|
# if more then we failed
|
||||||
if eval_stack.size() != 1:
|
parser.compiler.assert(
|
||||||
printerr('[%s] Error parsing expression (stack did not reduce correctly )' % first)
|
eval_stack.size() == 1,
|
||||||
|
'[%s] Error parsing expression (stack did not reduce correctly)' % first,
|
||||||
|
first.line_number,
|
||||||
|
first.column
|
||||||
|
)
|
||||||
|
|
||||||
return eval_stack.pop_back()
|
return eval_stack.pop_back()
|
||||||
|
|
||||||
|
@ -860,12 +899,13 @@ class ExpressionNode extends ParseNode:
|
||||||
return key
|
return key
|
||||||
return string
|
return string
|
||||||
|
|
||||||
static func is_apply_precedence(_type, operator_stack):
|
static func is_apply_precedence(_type, operator_stack, parser):
|
||||||
if operator_stack.size() == 0:
|
if operator_stack.size() == 0:
|
||||||
return false
|
return false
|
||||||
|
|
||||||
if not Operator.is_op(_type):
|
if not Operator.is_op(_type):
|
||||||
assert(false, 'Unable to parse expression!')
|
parser.compiler.assert(false, 'Unable to parse expression!')
|
||||||
|
return false
|
||||||
|
|
||||||
var second = operator_stack.back().type
|
var second = operator_stack.back().type
|
||||||
|
|
||||||
|
|
|
@ -211,3 +211,4 @@ static func token_name(type):
|
||||||
if TokenType[key] == type:
|
if TokenType[key] == type:
|
||||||
return key
|
return key
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,6 @@ class Line:
|
||||||
func _to_string():
|
func _to_string():
|
||||||
return '%s:%d: "%s"' % [file_name.get_file(), line_number, text]
|
return '%s:%d: "%s"' % [file_name.get_file(), line_number, text]
|
||||||
|
|
||||||
|
|
||||||
class Option:
|
class Option:
|
||||||
var line
|
var line
|
||||||
var id = -1
|
var id = -1
|
||||||
|
|
56
addons/Wol/editor/Editor.gd
Normal file
56
addons/Wol/editor/Editor.gd
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
tool
|
||||||
|
extends Panel
|
||||||
|
|
||||||
|
var current_node
|
||||||
|
var current_graph_node
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
hide()
|
||||||
|
connect('visibility_changed', self, '_on_visibility_changed')
|
||||||
|
$Close.connect('pressed', self, 'close')
|
||||||
|
|
||||||
|
func close():
|
||||||
|
hide()
|
||||||
|
|
||||||
|
func open_node(graph_node, node):
|
||||||
|
current_node = node
|
||||||
|
current_graph_node = graph_node
|
||||||
|
|
||||||
|
var text_edit = graph_node.get_node('TextEdit')
|
||||||
|
text_edit.get_parent().remove_child(text_edit)
|
||||||
|
$Content.add_child(text_edit)
|
||||||
|
toggle_text_edit(text_edit)
|
||||||
|
|
||||||
|
show()
|
||||||
|
|
||||||
|
# window_title = node.title
|
||||||
|
|
||||||
|
func toggle_text_edit(text_edit):
|
||||||
|
text_edit.anchor_left = 0
|
||||||
|
text_edit.anchor_top = 0
|
||||||
|
text_edit.anchor_bottom = 1
|
||||||
|
text_edit.anchor_right = 1
|
||||||
|
text_edit.margin_left = 0
|
||||||
|
text_edit.margin_right = 0
|
||||||
|
text_edit.margin_bottom = 0
|
||||||
|
text_edit.margin_top = 0
|
||||||
|
text_edit.mouse_filter = MOUSE_FILTER_STOP if text_edit.get_parent().name == 'Content' else MOUSE_FILTER_IGNORE
|
||||||
|
|
||||||
|
text_edit.deselect()
|
||||||
|
|
||||||
|
for property in [
|
||||||
|
'highlight_current_line',
|
||||||
|
'show_line_numbers',
|
||||||
|
'draw_tabs',
|
||||||
|
'smooth_scrolling',
|
||||||
|
'wrap_enabled',
|
||||||
|
'minimap_draw'
|
||||||
|
]:
|
||||||
|
text_edit.set(property, not text_edit.get(property))
|
||||||
|
|
||||||
|
func _on_visibility_changed():
|
||||||
|
if not visible:
|
||||||
|
var text_edit = $Content/TextEdit
|
||||||
|
$Content.remove_child(text_edit)
|
||||||
|
current_graph_node.add_child(text_edit)
|
||||||
|
toggle_text_edit(text_edit)
|
48
addons/Wol/editor/GraphNodeTemplate.tscn
Normal file
48
addons/Wol/editor/GraphNodeTemplate.tscn
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
[gd_scene load_steps=3 format=2]
|
||||||
|
|
||||||
|
[ext_resource path="res://addons/Wol/editor/WolGraphNode.gd" type="Script" id=1]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id=1]
|
||||||
|
content_margin_left = 4.0
|
||||||
|
content_margin_right = 4.0
|
||||||
|
content_margin_top = 26.0
|
||||||
|
content_margin_bottom = 4.0
|
||||||
|
corner_radius_top_left = 4
|
||||||
|
corner_radius_top_right = 4
|
||||||
|
corner_radius_bottom_right = 4
|
||||||
|
corner_radius_bottom_left = 4
|
||||||
|
|
||||||
|
[node name="GraphNodeTemplate" type="GraphNode"]
|
||||||
|
margin_left = 500.0
|
||||||
|
margin_top = 1068.0
|
||||||
|
margin_right = 937.0
|
||||||
|
margin_bottom = 1459.0
|
||||||
|
mouse_filter = 1
|
||||||
|
custom_styles/frame = SubResource( 1 )
|
||||||
|
title = "Hello world"
|
||||||
|
offset = Vector2( 500, 500 )
|
||||||
|
resizable = true
|
||||||
|
slot/0/left_enabled = false
|
||||||
|
slot/0/left_type = 0
|
||||||
|
slot/0/left_color = Color( 1, 1, 1, 1 )
|
||||||
|
slot/0/right_enabled = false
|
||||||
|
slot/0/right_type = 0
|
||||||
|
slot/0/right_color = Color( 1, 1, 1, 1 )
|
||||||
|
script = ExtResource( 1 )
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[node name="TextEdit" type="TextEdit" parent="."]
|
||||||
|
margin_left = 4.0
|
||||||
|
margin_top = 26.0
|
||||||
|
margin_right = 433.0
|
||||||
|
margin_bottom = 387.0
|
||||||
|
mouse_filter = 2
|
||||||
|
size_flags_horizontal = 3
|
||||||
|
size_flags_vertical = 3
|
||||||
|
syntax_highlighting = true
|
||||||
|
fold_gutter = true
|
||||||
|
context_menu_enabled = false
|
||||||
|
virtual_keyboard_enabled = false
|
||||||
|
wrap_enabled = true
|
103
addons/Wol/editor/WolEditor.gd
Normal file
103
addons/Wol/editor/WolEditor.gd
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
tool
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
const Compiler = preload('res://addons/Wol/core/compiler/Compiler.gd')
|
||||||
|
onready var GraphNodeTemplate = $GraphNodeTemplate
|
||||||
|
|
||||||
|
var path
|
||||||
|
var compiler
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
for menu_button in [$Menu/File]:
|
||||||
|
menu_button.get_popup().connect('index_pressed', self, '_on_menu_pressed', [menu_button.get_popup()])
|
||||||
|
|
||||||
|
# TODO: Conditionally load in theme based on Editor or standalone
|
||||||
|
|
||||||
|
path = 'res://dialogue.yarn'
|
||||||
|
build_nodes()
|
||||||
|
|
||||||
|
func build_nodes():
|
||||||
|
compiler = Compiler.new(path)
|
||||||
|
|
||||||
|
for node in compiler.get_nodes():
|
||||||
|
var graph_node = GraphNodeTemplate.duplicate()
|
||||||
|
$GraphEdit.add_child(graph_node)
|
||||||
|
graph_node.node = node
|
||||||
|
graph_node.show()
|
||||||
|
graph_node.connect('gui_input', self, '_on_graph_node_input', [graph_node, node])
|
||||||
|
|
||||||
|
func serialize_to_file():
|
||||||
|
var buffer = []
|
||||||
|
for graph_node in $GraphEdit.get_children():
|
||||||
|
if not graph_node is GraphNode:
|
||||||
|
continue
|
||||||
|
|
||||||
|
var node = graph_node.node
|
||||||
|
buffer.append('title: %s' % node.title)
|
||||||
|
buffer.append('tags: ')
|
||||||
|
buffer.append('colorID: ')
|
||||||
|
buffer.append('position: %d, %d' % [node.position.x, node.position.y])
|
||||||
|
buffer.append('---')
|
||||||
|
buffer.append(node.body)
|
||||||
|
buffer.append('===')
|
||||||
|
|
||||||
|
return PoolStringArray(buffer).join('\n')
|
||||||
|
|
||||||
|
func save_as(file_path = null):
|
||||||
|
if not file_path:
|
||||||
|
$FileDialog.mode = $FileDialog.MODE_SAVE_FILE
|
||||||
|
# TODO: Set up path based on context (Godot editor, standalone or web)
|
||||||
|
$FileDialog.popup_centered()
|
||||||
|
file_path = yield($FileDialog, 'file_selected')
|
||||||
|
|
||||||
|
if not file_path:
|
||||||
|
return
|
||||||
|
|
||||||
|
var file = File.new()
|
||||||
|
file.open(file_path, File.WRITE)
|
||||||
|
file.store_string(serialize_to_file())
|
||||||
|
file.close()
|
||||||
|
print('saved file!')
|
||||||
|
|
||||||
|
func open():
|
||||||
|
$FileDialog.mode = $FileDialog.MODE_OPEN_FILE
|
||||||
|
# TODO: Set up path based on context (Godot editor, standalone or web)
|
||||||
|
$FileDialog.popup_centered()
|
||||||
|
path = yield($FileDialog, 'file_selected')
|
||||||
|
if not path:
|
||||||
|
return
|
||||||
|
|
||||||
|
for node in $GraphEdit.get_children():
|
||||||
|
if node is GraphNode:
|
||||||
|
$GraphEdit.remove_child(node)
|
||||||
|
node.queue_free()
|
||||||
|
|
||||||
|
yield(get_tree(), 'idle_frame')
|
||||||
|
build_nodes()
|
||||||
|
|
||||||
|
func new():
|
||||||
|
# TODO: add dialog for maybe saving existing file
|
||||||
|
|
||||||
|
for node in $GraphEdit.get_children():
|
||||||
|
if node is GraphNode:
|
||||||
|
$GraphEdit.remove_child(node)
|
||||||
|
node.queue_free()
|
||||||
|
|
||||||
|
path = null
|
||||||
|
|
||||||
|
func _on_menu_pressed(index, node):
|
||||||
|
match(node.get_item_text(index)):
|
||||||
|
'New':
|
||||||
|
new()
|
||||||
|
'Save':
|
||||||
|
save_as(path)
|
||||||
|
'Save as...':
|
||||||
|
save_as()
|
||||||
|
'Open':
|
||||||
|
open()
|
||||||
|
|
||||||
|
func _on_graph_node_input(event, graph_node, node):
|
||||||
|
if event is InputEventMouseButton \
|
||||||
|
and event.doubleclick and event.button_index == BUTTON_LEFT:
|
||||||
|
$HBoxContainer/Editor.open_node(graph_node, node)
|
||||||
|
accept_event()
|
BIN
addons/Wol/editor/WolEditor.tscn
(Stored with Git LFS)
Normal file
BIN
addons/Wol/editor/WolEditor.tscn
(Stored with Git LFS)
Normal file
Binary file not shown.
46
addons/Wol/editor/WolGraphNode.gd
Normal file
46
addons/Wol/editor/WolGraphNode.gd
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
tool
|
||||||
|
extends GraphNode
|
||||||
|
|
||||||
|
const Compiler = preload('res://addons/Wol/core/compiler/Compiler.gd')
|
||||||
|
|
||||||
|
var node setget set_node
|
||||||
|
|
||||||
|
onready var text_edit = $TextEdit
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
connect('offset_changed', self, '_on_offset_changed')
|
||||||
|
text_edit.connect('text_changed', self, '_on_text_changed')
|
||||||
|
$TextDebounce.connect('timeout', self, '_on_debounce')
|
||||||
|
|
||||||
|
func _on_text_changed():
|
||||||
|
$TextDebounce.start(.3)
|
||||||
|
|
||||||
|
func _on_debounce():
|
||||||
|
text_edit.get_node('ErrorGutter').hide()
|
||||||
|
node.body = text_edit.text
|
||||||
|
compile()
|
||||||
|
|
||||||
|
func _on_offset_changed():
|
||||||
|
node.position = offset
|
||||||
|
|
||||||
|
func _on_error(message, _line_number, _column):
|
||||||
|
var error_gutter = text_edit.get_node('ErrorGutter')
|
||||||
|
error_gutter.show()
|
||||||
|
error_gutter.text = message
|
||||||
|
|
||||||
|
# TODO: Highlight line based on line number and column
|
||||||
|
|
||||||
|
func set_node(_node):
|
||||||
|
node = _node
|
||||||
|
title = node.title
|
||||||
|
text_edit.text = node.body
|
||||||
|
text_edit.clear_undo_history()
|
||||||
|
offset = node.position
|
||||||
|
|
||||||
|
compile()
|
||||||
|
|
||||||
|
func compile():
|
||||||
|
var text = '---\n%s\n===' % text_edit.text
|
||||||
|
var compiler = Compiler.new(null, text, true)
|
||||||
|
compiler.connect('error', self, '_on_error')
|
||||||
|
compiler.compile()
|
|
@ -1,6 +1,10 @@
|
||||||
tool
|
tool
|
||||||
extends EditorPlugin
|
extends EditorPlugin
|
||||||
|
|
||||||
|
const WolEditor = preload('res://addons/Wol/editor/WolEditor.tscn')
|
||||||
|
|
||||||
|
var wol_editor_instance
|
||||||
|
|
||||||
func _enter_tree():
|
func _enter_tree():
|
||||||
add_custom_type(
|
add_custom_type(
|
||||||
'Wol',
|
'Wol',
|
||||||
|
@ -9,5 +13,27 @@ func _enter_tree():
|
||||||
load('res://addons/Wol/icon-white.svg')
|
load('res://addons/Wol/icon-white.svg')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
wol_editor_instance = WolEditor.instance()
|
||||||
|
get_editor_interface().get_editor_viewport().add_child(wol_editor_instance)
|
||||||
|
|
||||||
|
make_visible(false)
|
||||||
|
|
||||||
|
func make_visible(visible):
|
||||||
|
if wol_editor_instance:
|
||||||
|
wol_editor_instance.visible = visible
|
||||||
|
|
||||||
func _exit_tree():
|
func _exit_tree():
|
||||||
remove_custom_type('Wol')
|
remove_custom_type('Wol')
|
||||||
|
|
||||||
|
if wol_editor_instance:
|
||||||
|
wol_editor_instance.queue_free()
|
||||||
|
|
||||||
|
func has_main_screen():
|
||||||
|
return true
|
||||||
|
|
||||||
|
func get_plugin_name():
|
||||||
|
return 'Wol'
|
||||||
|
|
||||||
|
func get_plugin_icon():
|
||||||
|
print(get_editor_interface().get_base_control().get_icon('Node', 'EditorIcons'))
|
||||||
|
return get_editor_interface().get_base_control().get_icon('Node', 'EditorIcons')
|
||||||
|
|
|
@ -1,54 +1,54 @@
|
||||||
title: Start
|
title: Start
|
||||||
tags:
|
tags:
|
||||||
colorID: 0
|
colorID:
|
||||||
position: 0, 0
|
position: 0, 0
|
||||||
---
|
---
|
||||||
<<a_custom_command>>
|
<<a_custom_command>>
|
||||||
<<command_with multiple arguments>>
|
<<command_with multiple arguments>>
|
||||||
|
|
||||||
// remove "to" to trigger error
|
// remove "to" to trigger error
|
||||||
<<set $direction to 'that'>>
|
<<set $direction to "that">>
|
||||||
<<set $one to 1>>
|
<<set $one to 1>>
|
||||||
|
|
||||||
// Implement inline expressions
|
// Implement inline expressions
|
||||||
<<if visit_count() == 1>>
|
<<if visit_count() == 1>>
|
||||||
Narrator: You, {$direction} way!
|
Narrator: You, {$direction} way!
|
||||||
<<endif>>
|
<<endif>>
|
||||||
Narrator: Do you know you've been here {visit_count()} times?
|
Narrator: Do you know you've been here {visit_count()} times?
|
||||||
You: Did you know one + one equals {$one + $one}?
|
You: Did you know one + one equals {$one + $one}?
|
||||||
Narrator: You wanna go somewhere?
|
Narrator: You wanna go somewhere?
|
||||||
|
|
||||||
-> Go to the store
|
-> Go to the store
|
||||||
[[TheStore]]
|
[[TheStore]]
|
||||||
// -> Wait, how many times have I been to the store?
|
-> How much did I visit the store?
|
||||||
// Narrator: You've been to the store {visit_count('TheStore')} times.
|
Narrator: You've been to the store { visit_count("TheStore") } times.
|
||||||
// [[Start]]
|
[[Start]]
|
||||||
-> Lets stay here and talk
|
-> Lets stay here and talk
|
||||||
[[Talk]]
|
[[Talk]]
|
||||||
===
|
===
|
||||||
title: TheStore
|
title: TheStore
|
||||||
tags:
|
tags:
|
||||||
colorID: 0
|
colorID:
|
||||||
position: 0, 200
|
position: 400, 200
|
||||||
---
|
---
|
||||||
Guy: Hey what's up I need your help can you come here?
|
Guy: Hey what's up I need your help can you come here?
|
||||||
You: Well I can't I'm buying clothes.
|
You: Well I can't I'm buying clothes.
|
||||||
All right well hurry up and come over here.
|
Guy: All right well hurry up and come over here.
|
||||||
You: I can't find them.
|
You: I can't find them.
|
||||||
Guy: What do you mean you can't find them?
|
Guy: What do you mean you can't find them?
|
||||||
You: I can't find them there's only soup.
|
You: I can't find them there's only soup.
|
||||||
Guy: What do you mean there's only soup?!
|
Guy: What do you mean there's only soup?!
|
||||||
You: It means there's only soup.
|
You: It means there's only soup.
|
||||||
Guy: WELL THEN GET OUT OF THE SOUP ISLE!!
|
Guy: WELL THEN GET OUT OF THE SOUP ISLE!!
|
||||||
You: Alright you dont have to shout at me!
|
You: Alright you dont have to shout at me!
|
||||||
You: There's more soup.
|
You: There's more soup.
|
||||||
Guy: What do you mean there's more soup?
|
Guy: What do you mean there's more soup?
|
||||||
You: There's just more soup.
|
You: There's just more soup.
|
||||||
Guy: Then go to the next aisle!
|
Guy: Then go to the next aisle!
|
||||||
You: There's still soup!
|
You: There's still soup!
|
||||||
Guy: Where are you right now?!
|
Guy: Where are you right now?!
|
||||||
You: I'm at soup!
|
You: I'm at soup!
|
||||||
Guy: What do you mean you're at soup?!
|
Guy: What do you mean you're at soup?!
|
||||||
You: I mean I'm at soup.
|
You: I mean I'm at soup.
|
||||||
Guy: WHAT STORE ARE YOU IN?!
|
Guy: WHAT STORE ARE YOU IN?!
|
||||||
You: IM AT THE SOUP STORE!!
|
You: IM AT THE SOUP STORE!!
|
||||||
|
@ -58,8 +58,8 @@ You: FUCK YOU!
|
||||||
===
|
===
|
||||||
title: Talk
|
title: Talk
|
||||||
tags:
|
tags:
|
||||||
colorID: 0
|
colorID:
|
||||||
position: 0, 400
|
position: 800, 400
|
||||||
---
|
---
|
||||||
Narrator: So how are you really?
|
Narrator: So how are you really?
|
||||||
You: I'm good!
|
You: I'm good!
|
||||||
|
|
|
@ -24,6 +24,11 @@ gdscript/warnings/return_value_discarded=false
|
||||||
|
|
||||||
enabled=PoolStringArray( "res://addons/Wol/plugin.cfg" )
|
enabled=PoolStringArray( "res://addons/Wol/plugin.cfg" )
|
||||||
|
|
||||||
|
[network]
|
||||||
|
|
||||||
|
limits/debugger_stdout/max_chars_per_second=8096
|
||||||
|
limits/debugger_stdout/max_messages_per_frame=200
|
||||||
|
|
||||||
[physics]
|
[physics]
|
||||||
|
|
||||||
common/enable_pause_aware_picking=true
|
common/enable_pause_aware_picking=true
|
||||||
|
|
Reference in a new issue