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_exited', self, '_on_player_near_dialogue', [$Ship, false])
|
||||
|
||||
$Dialogue/Wol.connect('finished', self, '_on_finished')
|
||||
|
||||
func _on_player_near_dialogue(_player, node, entered):
|
||||
print('body entered?', entered)
|
||||
if entered:
|
||||
current_dialogue = node.name
|
||||
else:
|
||||
current_dialogue = null
|
||||
|
||||
func _on_finished():
|
||||
$DialogueCooldown.start()
|
||||
|
||||
func _process(_delta):
|
||||
if Input.is_action_just_released('ui_accept') and current_dialogue and not $Dialogue/Wol.running:
|
||||
print(current_dialogue)
|
||||
if Input.is_action_just_released('ui_accept') \
|
||||
and current_dialogue and not $Dialogue/Wol.running and $DialogueCooldown.time_left == 0:
|
||||
$Dialogue/Wol.starting_node = current_dialogue
|
||||
$Dialogue/Wol.path = 'res://ExampleDialogue/%s.yarn' % current_dialogue
|
||||
$Dialogue/Wol.start()
|
||||
print($Dialogue/Wol.variable_storage)
|
||||
|
|
|
@ -230,3 +230,7 @@ __meta__ = {
|
|||
|
||||
[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 )
|
||||
|
||||
[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:
|
||||
node = virtual_machine.program.strings[node.value()].text
|
||||
|
||||
|
||||
var variable_storage = virtual_machine.dialogue.variable_storage
|
||||
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
|
||||
|
|
|
@ -68,7 +68,7 @@ func set_node(name):
|
|||
dialogue.variable_storage[program.filename] = {}
|
||||
|
||||
if not dialogue.variable_storage[program.filename].has(name):
|
||||
dialogue.variable_storage[program.filename][name] = 0
|
||||
dialogue.variable_storage[program.filename][name] = 1
|
||||
else:
|
||||
dialogue.variable_storage[program.filename][name] += 1
|
||||
return true
|
||||
|
@ -287,7 +287,7 @@ func run_instruction(instruction):
|
|||
var line = option[0]
|
||||
var destination = option[1]
|
||||
choices.append(Program.Option.new(line, option_index, destination))
|
||||
|
||||
|
||||
execution_state = Constants.ExecutionState.WaitingForOption
|
||||
options_handler.call_func(choices)
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
extends Object
|
||||
|
||||
signal error(message, line_number, column)
|
||||
|
||||
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')
|
||||
|
@ -12,13 +14,15 @@ var filename = ''
|
|||
|
||||
var current_node
|
||||
var has_implicit_string_tags = false
|
||||
var soft_assert = false
|
||||
|
||||
var string_count = 0
|
||||
var string_table = {}
|
||||
var label_count = 0
|
||||
|
||||
func _init(_filename, _source = null):
|
||||
func _init(_filename, _source = null, _soft_assert = false):
|
||||
filename = _filename
|
||||
soft_assert = _soft_assert
|
||||
|
||||
if not _filename and _source:
|
||||
self.source = _source
|
||||
|
@ -28,69 +32,119 @@ func _init(_filename, _source = null):
|
|||
self.source = file.get_as_text()
|
||||
file.close()
|
||||
|
||||
func compile():
|
||||
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)
|
||||
var source_lines = source.split('\n')
|
||||
for i in range(source_lines.size()):
|
||||
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():
|
||||
var title = ''
|
||||
var body = ''
|
||||
|
||||
# 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 = []
|
||||
var line = source_lines[line_number]
|
||||
line_number += 1
|
||||
|
||||
while line_number < source_lines.size() and source_lines[line_number] != '===':
|
||||
body_lines.append(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)
|
||||
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
|
||||
|
||||
body = PoolStringArray(body_lines).join('\n')
|
||||
var lexer = Lexer.new(filename, title, body)
|
||||
var tokens = lexer.tokenize()
|
||||
line_number += 1
|
||||
|
||||
var parser = Parser.new(title, tokens)
|
||||
var parser_node = parser.parse_node()
|
||||
return PoolStringArray(body_lines).join('\n')
|
||||
|
||||
parser_node.name = title
|
||||
# parser_node.tags = title
|
||||
parsed_nodes.append(parser_node)
|
||||
func get_nodes():
|
||||
var nodes = []
|
||||
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():
|
||||
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()
|
||||
program.filename = filename
|
||||
|
@ -104,7 +158,7 @@ func compile():
|
|||
return program
|
||||
|
||||
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()
|
||||
|
||||
|
@ -205,7 +259,7 @@ func generate_statement(node, statement):
|
|||
Constants.StatementTypes.Line:
|
||||
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):
|
||||
# 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 labels = []
|
||||
var option_count = 0
|
||||
|
||||
|
||||
for option in shortcut_group.options:
|
||||
var endof_clause = ''
|
||||
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 LINE_COMENT = '//'
|
||||
const LINE_COMMENT = '//'
|
||||
const FORWARD_SLASH = '/'
|
||||
const LINE_SEPARATOR = '\n'
|
||||
|
||||
|
@ -22,6 +22,7 @@ const FORMAT_FUNCTION = 'format'
|
|||
|
||||
var WHITESPACE = '\\s*'
|
||||
|
||||
var compiler
|
||||
var filename = ''
|
||||
var title = ''
|
||||
var text = ''
|
||||
|
@ -33,14 +34,15 @@ var current_state
|
|||
var indent_stack = []
|
||||
var should_track_indent = false
|
||||
|
||||
func _init(_filename, _title, _text):
|
||||
createstates()
|
||||
func _init(_compiler, _filename, _title, _text):
|
||||
create_states()
|
||||
|
||||
compiler = _compiler
|
||||
filename = _filename
|
||||
title = _title
|
||||
text = _text
|
||||
|
||||
func createstates():
|
||||
func create_states():
|
||||
var patterns = {}
|
||||
patterns[Constants.TokenType.Text] = ['.*', 'any text']
|
||||
|
||||
|
@ -111,8 +113,8 @@ func createstates():
|
|||
states[BASE].add_transition(Constants.TokenType.TagMarker, TAG, true)
|
||||
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}
|
||||
# Possible solution is to add more transitions
|
||||
#FIXME - Tags are not being proccessed properly this way. We must look for the format #{identifier}:{value}
|
||||
# Possible solution is to add more transitions
|
||||
states[TAG] = LexerState.new(patterns)
|
||||
states[TAG].add_transition(Constants.TokenType.Identifier, BASE)
|
||||
|
||||
|
@ -236,7 +238,11 @@ func tokenize():
|
|||
lines.append('')
|
||||
|
||||
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
|
||||
|
||||
var end_of_input = Token.new(
|
||||
|
@ -252,12 +258,12 @@ func tokenize():
|
|||
func tokenize_line(line, line_number):
|
||||
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 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])
|
||||
|
||||
var indent = Token.new(
|
||||
|
@ -284,7 +290,7 @@ func tokenize_line(line, line_number):
|
|||
whitespace.compile(WHITESPACE)
|
||||
|
||||
while column < fresh_line.length():
|
||||
if fresh_line.substr(column).begins_with(LINE_COMENT):
|
||||
if fresh_line.substr(column).begins_with(LINE_COMMENT):
|
||||
break
|
||||
|
||||
var matched = false
|
||||
|
@ -367,12 +373,12 @@ func tokenize_line(line, line_number):
|
|||
line_number,
|
||||
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)
|
||||
if last_whitespace:
|
||||
column += last_whitespace.get_string().length()
|
||||
|
||||
|
||||
token_stack.invert()
|
||||
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
extends Object
|
||||
|
||||
# warnings-disable
|
||||
|
||||
const Constants = preload('res://addons/Wol/core/Constants.gd')
|
||||
const Lexer = preload('res://addons/Wol/core/compiler/Lexer.gd')
|
||||
const Value = preload('res://addons/Wol/core/Value.gd')
|
||||
|
||||
var tokens = []
|
||||
var compiler
|
||||
var title = ''
|
||||
var tokens = []
|
||||
|
||||
func _init(_title, _tokens):
|
||||
func _init(_compiler, _title, _tokens):
|
||||
compiler = _compiler
|
||||
title = _title
|
||||
tokens = _tokens
|
||||
|
||||
|
@ -21,13 +25,9 @@ func parse_node():
|
|||
return WolNode.new('Start', null, self)
|
||||
|
||||
func next_symbol_is(valid_types):
|
||||
var type = self.tokens.front().type
|
||||
for valid_type in valid_types:
|
||||
if type == valid_type:
|
||||
return true
|
||||
return false
|
||||
return tokens.front().type in valid_types
|
||||
|
||||
# NOTE:0 look ahead for `<<` and `else`
|
||||
# NOTE: 0 look ahead for `<<` and `else`
|
||||
func next_symbols_are(valid_types):
|
||||
var temporary = [] + tokens
|
||||
for type in valid_types:
|
||||
|
@ -40,7 +40,7 @@ func expect_symbol(token_types = []):
|
|||
|
||||
if token_types.size() == 0:
|
||||
if token.type == Constants.TokenType.EndOfInput:
|
||||
assert(false, 'Unexpected end of input')
|
||||
compiler.assert(false, 'Unexpected end of input')
|
||||
return token
|
||||
|
||||
for type in token_types:
|
||||
|
@ -60,15 +60,12 @@ func expect_symbol(token_types = []):
|
|||
error_guess = ''
|
||||
|
||||
var error_data = [
|
||||
token.filename,
|
||||
title,
|
||||
token.line_number,
|
||||
token.column,
|
||||
PoolStringArray(token_names).join(', '),
|
||||
Constants.token_type_name(token.type),
|
||||
error_guess
|
||||
]
|
||||
assert(false, '[%s|%s:%d:%d]:\nExpected token "%s" but got "%s"%s' % error_data)
|
||||
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):
|
||||
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 statements = []
|
||||
var parser
|
||||
|
||||
func _init(name, parent, parser).(parent, parser):
|
||||
self.name = name
|
||||
func _init(_name, parent, _parser).(parent, _parser):
|
||||
name = _name
|
||||
parser = _parser
|
||||
while parser.tokens.size() > 0 \
|
||||
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))
|
||||
|
||||
func tree_string(indent_level):
|
||||
|
@ -125,109 +132,6 @@ class WolNode extends ParseNode:
|
|||
|
||||
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:
|
||||
var Type = Constants.StatementTypes
|
||||
|
||||
|
@ -270,7 +174,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())
|
||||
parser.compiler.assert(false, 'Expected a statement but got %s instead. (probably an imbalanced if statement)' % parser.tokens.front()._to_string())
|
||||
|
||||
var tags = []
|
||||
|
||||
|
@ -305,6 +209,107 @@ class Statement extends ParseNode:
|
|||
|
||||
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:
|
||||
|
||||
enum Type {
|
||||
|
@ -322,6 +327,7 @@ class CustomCommand extends ParseNode:
|
|||
var command_tokens = []
|
||||
command_tokens.append(parser.expect_symbol())
|
||||
|
||||
# FIXME: add exit condition
|
||||
while not parser.next_symbol_is([Constants.TokenType.EndCommand]):
|
||||
command_tokens.append(parser.expect_symbol())
|
||||
|
||||
|
@ -338,9 +344,9 @@ class CustomCommand extends ParseNode:
|
|||
type = Type.Expression
|
||||
|
||||
else:
|
||||
#otherwise evaluuate command
|
||||
# otherwise evaluate command
|
||||
type = Type.ClientCommand
|
||||
self.client_command = command_tokens[0].value
|
||||
client_command = command_tokens[0].value
|
||||
|
||||
func tree_string(indent_level):
|
||||
match type:
|
||||
|
@ -362,9 +368,7 @@ class ShortcutOptionGroup extends ParseNode:
|
|||
# parse options until there is no more
|
||||
# expect one otherwise invalid
|
||||
|
||||
var index = 1
|
||||
options.append(ShortCutOption.new(index, self, parser))
|
||||
index += 1
|
||||
var index = 0
|
||||
while parser.next_symbol_is([Constants.TokenType.ShortcutOption]):
|
||||
options.append(ShortCutOption.new(index, self, parser))
|
||||
index += 1
|
||||
|
@ -393,7 +397,7 @@ class ShortCutOption extends ParseNode:
|
|||
parser.expect_symbol([Constants.TokenType.ShortcutOption])
|
||||
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 = []
|
||||
while parser.next_symbols_are([Constants.TokenType.BeginCommand, Constants.TokenType.IfToken]) \
|
||||
or parser.next_symbol_is([Constants.TokenType.TagMarker]):
|
||||
|
@ -446,6 +450,7 @@ class Block extends ParseNode:
|
|||
parser.expect_symbol([Constants.TokenType.Indent])
|
||||
|
||||
#keep reading statements until we hit a dedent
|
||||
# FIXME: find exit condition
|
||||
while not parser.next_symbol_is([Constants.TokenType.Dedent]):
|
||||
#parse all statements including nested blocks
|
||||
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,
|
||||
# & then build a tree of expressions
|
||||
|
||||
# TODO: Rework expression parsing
|
||||
static func parse(parent, parser):
|
||||
var rpn = []
|
||||
var op_stack = []
|
||||
|
@ -693,20 +700,30 @@ class ExpressionNode extends ParseNode:
|
|||
|
||||
var last
|
||||
|
||||
print(parser.tokens.slice(0, 6))
|
||||
#read expression content
|
||||
while parser.tokens.size() > 0 and parser.next_symbol_is(valid_types):
|
||||
print(parser.tokens.front())
|
||||
var next = parser.expect_symbol(valid_types)
|
||||
|
||||
if next.type == Constants.TokenType.Variable \
|
||||
or next.type == Constants.TokenType.Number \
|
||||
or next.type == Constants.TokenType.Str \
|
||||
or next.type == Constants.TokenType.FalseToken \
|
||||
or next.type == Constants.TokenType.TrueToken \
|
||||
or next.type == Constants.TokenType.NullToken:
|
||||
|
||||
#output primitives
|
||||
rpn.append(next)
|
||||
if next.type in [
|
||||
Constants.TokenType.Variable,
|
||||
Constants.TokenType.Number,
|
||||
Constants.TokenType.Str,
|
||||
Constants.TokenType.FalseToken,
|
||||
Constants.TokenType.TrueToken,
|
||||
Constants.TokenType.NullToken
|
||||
]:
|
||||
|
||||
# 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:
|
||||
print('adding function')
|
||||
op_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:
|
||||
var p = op_stack.pop_back()
|
||||
if p == null:
|
||||
printerr('unbalanced parenthesis %s ' % next.name)
|
||||
printerr('unbalanced parenthesis %s' % next.name)
|
||||
break
|
||||
rpn.append(p)
|
||||
|
||||
|
@ -732,7 +749,7 @@ class ExpressionNode extends ParseNode:
|
|||
|
||||
#find the closest function on stack
|
||||
#increment parameters
|
||||
func_stack.back().parameter_count+=1
|
||||
func_stack.back().parameter_count += 1
|
||||
|
||||
elif Operator.is_op(next.type):
|
||||
#this is an operator
|
||||
|
@ -759,7 +776,7 @@ class ExpressionNode extends ParseNode:
|
|||
next.type = Constants.TokenType.EqualTo
|
||||
|
||||
#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()
|
||||
rpn.append(op)
|
||||
|
||||
|
@ -771,20 +788,28 @@ class ExpressionNode extends ParseNode:
|
|||
elif next.type == Constants.TokenType.RightParen:
|
||||
#leaving sub expression
|
||||
# resolve order of operations
|
||||
var parameters = []
|
||||
while op_stack.back().type != Constants.TokenType.LeftParen:
|
||||
rpn.append(op_stack.pop_back())
|
||||
if op_stack.back() == null:
|
||||
printerr('Unbalanced parenthasis #RightParen. Parser.ExpressionNode')
|
||||
parameters.append(op_stack.pop_back())
|
||||
|
||||
parser.compiler.assert(
|
||||
op_stack.back() != null,
|
||||
'Unbalanced parenthasis #RightParen. Parser.ExpressionNode'
|
||||
)
|
||||
|
||||
|
||||
rpn.append_array(parameters)
|
||||
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:
|
||||
#function call
|
||||
#last token == left paren this == no parameters
|
||||
#else
|
||||
#we have more than 1 param
|
||||
if last.type != Constants.TokenType.LeftParen:
|
||||
func_stack.back().parameter_count+=1
|
||||
# if last.type != Constants.TokenType.LeftParen:
|
||||
# func_stack.back().parameter_count += 1
|
||||
func_stack.back().parameter_count = parameters.size()
|
||||
|
||||
rpn.append(op_stack.pop_back())
|
||||
func_stack.pop_back()
|
||||
|
@ -804,6 +829,8 @@ class ExpressionNode extends ParseNode:
|
|||
var first = rpn.front()
|
||||
var eval_stack = []#ExpressionNode
|
||||
|
||||
print(rpn)
|
||||
|
||||
while rpn.size() > 0:
|
||||
var next = rpn.pop_front()
|
||||
if Operator.is_op(next.type):
|
||||
|
@ -811,7 +838,14 @@ class ExpressionNode extends ParseNode:
|
|||
var info = Operator.op_info(next.type)
|
||||
|
||||
if eval_stack.size() < info.arguments:
|
||||
printerr('Error parsing : Not enough arguments for %s [ got %s expected - was %s]'%[Constants.token_type_name(next.type), eval_stack.size(), info.arguments])
|
||||
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 = []
|
||||
for _i in range(info.arguments):
|
||||
|
@ -827,6 +861,7 @@ class ExpressionNode extends ParseNode:
|
|||
# A function call
|
||||
elif next.type == Constants.TokenType.Identifier:
|
||||
var function_name = next.value
|
||||
prints(function_name, next.parameter_count)
|
||||
|
||||
var function_parameters = []
|
||||
for _i in range(next.parameter_count):
|
||||
|
@ -845,10 +880,14 @@ class ExpressionNode extends ParseNode:
|
|||
eval_stack.append(expression)
|
||||
|
||||
|
||||
#we should have a single root expression left
|
||||
#if more then we failed ---- NANI
|
||||
if eval_stack.size() != 1:
|
||||
printerr('[%s] Error parsing expression (stack did not reduce correctly )' % first)
|
||||
# NOTE: We should have a single root expression left
|
||||
# if more then we failed
|
||||
parser.compiler.assert(
|
||||
eval_stack.size() == 1,
|
||||
'[%s] Error parsing expression (stack did not reduce correctly)' % first,
|
||||
first.line_number,
|
||||
first.column
|
||||
)
|
||||
|
||||
return eval_stack.pop_back()
|
||||
|
||||
|
@ -860,12 +899,13 @@ class ExpressionNode extends ParseNode:
|
|||
return key
|
||||
return string
|
||||
|
||||
static func is_apply_precedence(_type, operator_stack):
|
||||
static func is_apply_precedence(_type, operator_stack, parser):
|
||||
if operator_stack.size() == 0:
|
||||
return false
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -211,3 +211,4 @@ static func token_name(type):
|
|||
if TokenType[key] == type:
|
||||
return key
|
||||
return ''
|
||||
|
||||
|
|
|
@ -29,7 +29,6 @@ class Line:
|
|||
func _to_string():
|
||||
return '%s:%d: "%s"' % [file_name.get_file(), line_number, text]
|
||||
|
||||
|
||||
class Option:
|
||||
var line
|
||||
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
|
||||
extends EditorPlugin
|
||||
|
||||
const WolEditor = preload('res://addons/Wol/editor/WolEditor.tscn')
|
||||
|
||||
var wol_editor_instance
|
||||
|
||||
func _enter_tree():
|
||||
add_custom_type(
|
||||
'Wol',
|
||||
|
@ -9,5 +13,27 @@ func _enter_tree():
|
|||
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():
|
||||
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
|
||||
tags:
|
||||
colorID: 0
|
||||
colorID:
|
||||
position: 0, 0
|
||||
---
|
||||
<<a_custom_command>>
|
||||
<<command_with multiple arguments>>
|
||||
|
||||
// remove "to" to trigger error
|
||||
<<set $direction to 'that'>>
|
||||
<<set $direction to "that">>
|
||||
<<set $one to 1>>
|
||||
|
||||
// Implement inline expressions
|
||||
// Implement inline expressions
|
||||
<<if visit_count() == 1>>
|
||||
Narrator: You, {$direction} way!
|
||||
Narrator: You, {$direction} way!
|
||||
<<endif>>
|
||||
Narrator: Do you know you've been here {visit_count()} times?
|
||||
You: Did you know one + one equals {$one + $one}?
|
||||
Narrator: You wanna go somewhere?
|
||||
|
||||
-> Go to the store
|
||||
[[TheStore]]
|
||||
// -> Wait, how many times have I been to the store?
|
||||
// Narrator: You've been to the store {visit_count('TheStore')} times.
|
||||
// [[Start]]
|
||||
-> Go to the store
|
||||
[[TheStore]]
|
||||
-> How much did I visit the store?
|
||||
Narrator: You've been to the store { visit_count("TheStore") } times.
|
||||
[[Start]]
|
||||
-> Lets stay here and talk
|
||||
[[Talk]]
|
||||
===
|
||||
title: TheStore
|
||||
tags:
|
||||
colorID: 0
|
||||
position: 0, 200
|
||||
colorID:
|
||||
position: 400, 200
|
||||
---
|
||||
Guy: Hey what's up I need your help can you come here?
|
||||
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.
|
||||
Guy: What do you mean you can't find them?
|
||||
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.
|
||||
Guy: WELL THEN GET OUT OF THE SOUP ISLE!!
|
||||
You: Alright you dont have to shout at me!
|
||||
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.
|
||||
Guy: Then go to the next aisle!
|
||||
You: There's still soup!
|
||||
You: There's still soup!
|
||||
Guy: Where are you right now?!
|
||||
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.
|
||||
Guy: WHAT STORE ARE YOU IN?!
|
||||
You: IM AT THE SOUP STORE!!
|
||||
|
@ -58,8 +58,8 @@ You: FUCK YOU!
|
|||
===
|
||||
title: Talk
|
||||
tags:
|
||||
colorID: 0
|
||||
position: 0, 400
|
||||
colorID:
|
||||
position: 800, 400
|
||||
---
|
||||
Narrator: So how are you really?
|
||||
You: I'm good!
|
||||
|
|
|
@ -24,6 +24,11 @@ gdscript/warnings/return_value_discarded=false
|
|||
|
||||
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]
|
||||
|
||||
common/enable_pause_aware_picking=true
|
||||
|
|
Reference in a new issue