did a bunch things to finalize

This commit is contained in:
Bram Dingelstad 2021-11-21 23:10:48 +01:00
parent bad1dcb372
commit c4868130eb
14 changed files with 345 additions and 138 deletions

View file

@ -16,6 +16,8 @@ __meta__ = {
script = ExtResource( 2 ) script = ExtResource( 2 )
path = "res://dialogue.yarn" path = "res://dialogue.yarn"
auto_start = true auto_start = true
variable_storage = {
}
[node name="RichTextLabel" type="RichTextLabel" parent="."] [node name="RichTextLabel" type="RichTextLabel" parent="."]
anchor_right = 1.0 anchor_right = 1.0

View file

@ -17,15 +17,17 @@ signal started
signal finished signal finished
export(String, FILE, '*.wol,*.yarn') var path setget set_path export(String, FILE, '*.wol,*.yarn') var path setget set_path
export(String) var start_node = 'Start'
export(bool) var auto_start = false export var starting_node = 'Start'
export var auto_start = false
export var auto_show_options = true export var auto_show_options = true
export var auto_substitute = true
export(Dictionary) var variable_storage = {} export(Dictionary) var variable_storage = {}
const Constants = preload('res://addons/Wol/core/Constants.gd') const Constants = preload('res://addons/Wol/core/Constants.gd')
const WolCompiler = preload('res://addons/Wol/core/compiler/Compiler.gd') const Compiler = preload('res://addons/Wol/core/compiler/Compiler.gd')
const WolLibrary = preload('res://addons/Wol/core/Library.gd') const Library = preload('res://addons/Wol/core/Library.gd')
const VirtualMachine = preload('res://addons/Wol/core/VirtualMachine.gd') const VirtualMachine = preload('res://addons/Wol/core/VirtualMachine.gd')
const StandardLibrary = preload('res://addons/Wol/core/StandardLibrary.gd') const StandardLibrary = preload('res://addons/Wol/core/StandardLibrary.gd')
@ -35,10 +37,11 @@ func _ready():
if Engine.editor_hint: if Engine.editor_hint:
return return
var libraries = WolLibrary.new() var libraries = Library.new()
libraries.import_library(StandardLibrary.new())
virtual_machine = VirtualMachine.new(self, libraries) virtual_machine = VirtualMachine.new(self, libraries)
libraries.import_library(StandardLibrary.new())
set_path(path) set_path(path)
if auto_start: if auto_start:
@ -48,10 +51,16 @@ func set_path(_path):
path = _path path = _path
if not Engine.editor_hint and virtual_machine: if not Engine.editor_hint and virtual_machine:
var compiler = WolCompiler.new(path) var compiler = Compiler.new(path)
virtual_machine.program = compiler.compile() virtual_machine.program = compiler.compile()
func _on_line(line): func _on_line(line):
if auto_substitute:
var index = 0
for substitute in line.substitutions:
line.text = line.text.replace('{%d}' % index, substitute)
index += 1
call_deferred('emit_signal', 'line', line) call_deferred('emit_signal', 'line', line)
if auto_show_options \ if auto_show_options \
and virtual_machine.get_next_instruction().operation == Constants.ByteCode.AddOption: and virtual_machine.get_next_instruction().operation == Constants.ByteCode.AddOption:
@ -88,7 +97,7 @@ func select_option(id):
func pause(): func pause():
virtual_machine.call_deferred('pause') virtual_machine.call_deferred('pause')
func start(node = start_node): func start(node = starting_node):
emit_signal('started') emit_signal('started')
virtual_machine.set_node(node) virtual_machine.set_node(node)

View file

@ -1,5 +1,6 @@
extends Object extends Object
var Value : GDScript = load("res://addons/Wol/core/Value.gd")
const Value = preload("res://addons/Wol/core/Value.gd")
var name = '' var name = ''
# NOTE: -1 means variable arguments # NOTE: -1 means variable arguments
@ -18,12 +19,18 @@ func invoke(parameters = []):
if parameters != null: if parameters != null:
length = parameters.size() length = parameters.size()
if check_param_count(length): if check_parameter_count(length):
if returns_value: if returns_value:
var return_value
if length > 0: if length > 0:
return Value.new(function.call_funcv(parameters)) return_value = function.call_funcv(parameters)
else: else:
return Value.new(function.call_func()) return_value = function.call_func()
if return_value is Value:
return return_value
else:
return Value.new(return_value)
else: else:
if length > 0: if length > 0:
function.call_funcv(parameters) function.call_funcv(parameters)
@ -31,5 +38,5 @@ func invoke(parameters = []):
function.call_func() function.call_func()
return null return null
func check_param_count(_parameter_count): func check_parameter_count(_parameter_count):
return parameter_count == _parameter_count or parameter_count == -1 return parameter_count == _parameter_count or parameter_count == -1

View file

@ -72,25 +72,14 @@ func xor(param1, param2):
func lnot(param1): func lnot(param1):
return not param1.as_bool() return not param1.as_bool()
var visited_node_count = {} func is_node_visited(node = virtual_machine.current_node.name):
func is_node_visited(node = virtual_machine.current_node_name()):
return node_visit_count(node) > 0 return node_visit_count(node) > 0
func node_visit_count(node = virtual_machine.current_node_name()): 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 visit_count = 0 var variable_storage = virtual_machine.dialogue.variable_storage
if visited_node_count.has(node): var visited_node_count = variable_storage[virtual_machine.program.filename]
visit_count = visited_node_count[node]
return visit_count return visited_node_count[node] if visited_node_count.has(node) else 0
func get_visited_nodes():
return visited_node_count.keys()
func set_visited_nodes(visitedList):
visited_node_count.clear()
for string in visitedList:
visited_node_count[string] = 1

View file

@ -1,6 +1,7 @@
extends Node extends Object
const Constants = preload('res://addons/Wol/core/Constants.gd') const Constants = preload('res://addons/Wol/core/Constants.gd')
const Program = preload('res://addons/Wol/core/Program.gd')
const Value = preload('res://addons/Wol/core/Value.gd') const Value = preload('res://addons/Wol/core/Value.gd')
# Function references to handlers # Function references to handlers
@ -59,8 +60,17 @@ func set_node(name):
current_node = program.nodes[name] current_node = program.nodes[name]
reset() reset()
state.current_node_name = name state.current_node_name = name
node_start_handler.call_func(name) node_start_handler.call_func(name)
if not dialogue.variable_storage.has(program.filename):
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
return true return true
func pause(): func pause():
@ -91,8 +101,8 @@ func reset():
state = VmState.new() state = VmState.new()
func get_next_instruction(): func get_next_instruction():
if current_node.instructions.size() - 1 > state.programCounter: if current_node.instructions.size() - 1 > state.program_counter:
return current_node.instructions[state.programCounter + 1] return current_node.instructions[state.program_counter + 1]
return return
func start(): func start():
@ -115,14 +125,13 @@ func resume():
execution_state = Constants.ExecutionState.Running execution_state = Constants.ExecutionState.Running
#execute instruction until something cool happens
while execution_state == Constants.ExecutionState.Running: while execution_state == Constants.ExecutionState.Running:
var current_instruction = current_node.instructions[state.programCounter] var current_instruction = current_node.instructions[state.program_counter]
run_instruction(current_instruction) run_instruction(current_instruction)
state.programCounter += 1 state.program_counter += 1
if state.programCounter >= current_node.instructions.size(): if state.program_counter >= current_node.instructions.size():
node_finished_handler.call_func(current_node.nodeName) node_finished_handler.call_func(current_node.name)
execution_state = Constants.ExecutionState.Stopped execution_state = Constants.ExecutionState.Stopped
reset() reset()
dialogue_finished_handler.call_func() dialogue_finished_handler.call_func()
@ -142,17 +151,22 @@ func run_instruction(instruction):
# Jump to named label # Jump to named label
Constants.ByteCode.JumpTo: Constants.ByteCode.JumpTo:
state.programCounter = find_label_instruction(instruction.operands[0].value) - 1 state.program_counter = find_label_instruction(instruction.operands[0].value) - 1
Constants.ByteCode.RunLine: Constants.ByteCode.RunLine:
# Lookup string and give back as line # Lookup string and give back as line
var key = instruction.operands[0].value var key = instruction.operands[0].value
var line = program.strings[key] var line = program.strings[key].clone()
# The second operand is the expression count of format function # The second operand is the expression count of format function
# TODO: Add format functions supportk # TODO: Add format functions support
line.substitutions = []
if instruction.operands.size() > 1: if instruction.operands.size() > 1:
pass var expression_count = int(instruction.operands[1].value)
while expression_count > 0:
line.substitutions.append(state.pop_value().as_string())
expression_count -= 1
var return_state = line_handler.call_func(line) var return_state = line_handler.call_func(line)
@ -177,12 +191,12 @@ func run_instruction(instruction):
# Jump to named label if value of stack top is false # Jump to named label if value of stack top is false
Constants.ByteCode.JumpIfFalse: Constants.ByteCode.JumpIfFalse:
if not state.peek_value().as_bool(): if not state.peek_value().as_bool():
state.programCounter = find_label_instruction(instruction.operands[0].value) - 1 state.program_counter = find_label_instruction(instruction.operands[0].value) - 1
# Jump to label whose name is on the stack # Jump to label whose name is on the stack
Constants.ByteCode.Jump: Constants.ByteCode.Jump:
var destination = state.peek_value().as_string() var destination = state.peek_value().as_string()
state.programCounter = find_label_instruction(destination) - 1 state.program_counter = find_label_instruction(destination) - 1
Constants.ByteCode.Pop: Constants.ByteCode.Pop:
state.pop_value() state.pop_value()
@ -204,25 +218,29 @@ func run_instruction(instruction):
if actual_parameter_count == 0: if actual_parameter_count == 0:
result = function.invoke() result = function.invoke()
else: else:
var params = [] var parameters = []
for _i in range(actual_parameter_count): for _i in range(actual_parameter_count):
params.push_front(state.pop_value()) parameters.push_front(state.pop_value())
result = function.invoke(params) result = function.invoke(parameters)
if function.returns_value: if function.returns_value:
state.push_value(result) state.push_value(result)
Constants.ByteCode.PushVariable: Constants.ByteCode.PushVariable:
var name = instruction.operands[0].value var name = instruction.operands[0].value
var godot_value = dialogue.variable_storage[name] var value = dialogue.variable_storage[name.replace('$', '')]
var value = Value.new(godot_value)
state.push_value(value) state.push_value(value)
Constants.ByteCode.StoreVariable: Constants.ByteCode.StoreVariable:
var value = state.peek_value() var value = state.peek_value()
if value.type == Constants.ValueType.Str:
value = program.strings[value.value()].text
else:
value = value.value()
var name = instruction.operands[0].value.replace('$', '') var name = instruction.operands[0].value.replace('$', '')
dialogue.variable_storage[name] = value.value() dialogue.variable_storage[name] = value
Constants.ByteCode.Stop: Constants.ByteCode.Stop:
node_finished_handler.call_func(current_node.name) node_finished_handler.call_func(current_node.name)
@ -239,15 +257,15 @@ func run_instruction(instruction):
var return_state = node_finished_handler.call_func(current_node.name) var return_state = node_finished_handler.call_func(current_node.name)
set_node(name) set_node(name)
state.programCounter -= 1
if return_state == Constants.HandlerState.PauseExecution: if return_state == Constants.HandlerState.PauseExecution:
execution_state = Constants.ExecutionState.Suspended execution_state = Constants.ExecutionState.Suspended
Constants.ByteCode.AddOption: Constants.ByteCode.AddOption:
var key = instruction.operands[0].value var key = instruction.operands[0].value
var line = program.strings[key] var line = program.strings[key].clone()
# TODO: Add format functions supportk # TODO: Add format functions support
if instruction.operands.size() > 2: if instruction.operands.size() > 2:
pass pass
@ -280,11 +298,11 @@ func run_instruction(instruction):
class VmState: class VmState:
var current_node_name = '' var current_node_name = ''
var programCounter = 0 var program_counter = 0
var current_options = [] var current_options = []
var stack = [] var stack = []
func push_value(value)->void: func push_value(value):
if value is Value: if value is Value:
stack.push_back(value) stack.push_back(value)
else: else:

View file

@ -79,7 +79,6 @@ func compile():
line_number += 1 line_number += 1
body = PoolStringArray(body_lines).join('\n') body = PoolStringArray(body_lines).join('\n')
var lexer = Lexer.new(filename, title, body) var lexer = Lexer.new(filename, title, body)
var tokens = lexer.tokenize() var tokens = lexer.tokenize()
@ -94,6 +93,7 @@ func compile():
line_number += 1 line_number += 1
var program = Program.new() var program = Program.new()
program.filename = filename
for node in parsed_nodes: for node in parsed_nodes:
compile_node(program, node) compile_node(program, node)
@ -203,7 +203,7 @@ func generate_statement(node, statement):
generate_assignment(node, statement.assignment) generate_assignment(node, statement.assignment)
Constants.StatementTypes.Line: Constants.StatementTypes.Line:
generate_line(node, statement,statement.line) generate_line(node, statement)
_: _:
assert(false, 'Illegal statement type [%s]. Could not generate code.' % statement.type) assert(false, 'Illegal statement type [%s]. Could not generate code.' % statement.type)
@ -218,12 +218,19 @@ func generate_custom_command(node, command):
else : else :
emit(Constants.ByteCode.RunCommand, node, [Program.Operand.new(command_string)]) emit(Constants.ByteCode.RunCommand, node, [Program.Operand.new(command_string)])
func generate_line(node, statement, line): func generate_line(node, statement):
# TODO: Implement proper line numbers (global and local) # TODO: Implement proper line numbers (global and local)
var num = register_string(line, node.name, '', statement.line_number, []); var line = statement.line
emit(Constants.ByteCode.RunLine, node, [Program.Operand.new(num)]) var expression_count = line.substitutions.size()
func generate_shortcut_group(node,shortcut_group): while not line.substitutions.empty():
var inline_expression = line.substitutions.pop_back()
generate_expression(node, inline_expression.expression)
var num = register_string(line.line_text, node.name, line.line_id, statement.line_number, line.tags);
emit(Constants.ByteCode.RunLine, node,[Program.Operand.new(num), Program.Operand.new(expression_count)])
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
@ -243,7 +250,8 @@ func generate_shortcut_group(node,shortcut_group):
var label_string_id = register_string( var label_string_id = register_string(
option.label, option.label,
node.name, node.name,
label_line_id,option.line_number, label_line_id,
option.line_number,
[] []
) )
@ -287,7 +295,7 @@ func generate_if(node, if_statement):
generate_expression(node, clause.expression) generate_expression(node, clause.expression)
emit(Constants.ByteCode.JumpIfFalse, node, [Program.Operand.new(end_clause)]) emit(Constants.ByteCode.JumpIfFalse, node, [Program.Operand.new(end_clause)])
generate_block(node,clause.statements) generate_block(node, clause.statements)
emit(Constants.ByteCode.JumpTo, node, [Program.Operand.new(endif)]) emit(Constants.ByteCode.JumpTo, node, [Program.Operand.new(endif)])
if clause.expression != null: if clause.expression != null:

View file

@ -17,6 +17,8 @@ const ASSIGNMENT = 'assignment'
const OPTION = 'option' const OPTION = 'option'
const OR = 'or' const OR = 'or'
const DESTINATION = 'destination' const DESTINATION = 'destination'
const INLINE = 'inline'
const FORMAT_FUNCTION = 'format'
var WHITESPACE = '\\s*' var WHITESPACE = '\\s*'
@ -84,27 +86,40 @@ func createstates():
patterns[Constants.TokenType.EndIf] = ['endif(?!\\w)', '"endif"'] patterns[Constants.TokenType.EndIf] = ['endif(?!\\w)', '"endif"']
patterns[Constants.TokenType.Set] = ['set(?!\\w)', '"set"'] patterns[Constants.TokenType.Set] = ['set(?!\\w)', '"set"']
patterns[Constants.TokenType.ShortcutOption] = ['\\-\\>\\s*', '"->"'] patterns[Constants.TokenType.ShortcutOption] = ['\\-\\>\\s*', '"->"']
patterns[Constants.TokenType.ExpressionFunctionStart] = ['\\{', '"{"']
patterns[Constants.TokenType.ExpressionFunctionEnd] = ['\\}', '"}"']
patterns[Constants.TokenType.FormatFunctionStart] = ['(?<!\\[)\\[(?!\\[)', '"["']
patterns[Constants.TokenType.FormatFunctionEnd] = ['\\]', '"]"']
var shortcut_option = SHORTCUT + DASH + OPTION var shortcut_option = SHORTCUT + DASH + OPTION
var shortcut_option_tag = shortcut_option + DASH + TAG var shortcut_option_tag = shortcut_option + DASH + TAG
var command_or_expression = COMMAND + DASH + OR + DASH + EXPRESSION var command_or_expression = COMMAND + DASH + OR + DASH + EXPRESSION
var link_destination = LINK + DASH + DESTINATION var link_destination = LINK + DASH + DESTINATION
var format_expression = FORMAT_FUNCTION + DASH + EXPRESSION
var inline_expression = INLINE + DASH + EXPRESSION
var link_inline_expression = LINK + DASH + INLINE + DASH + EXPRESSION
var link_format_expression = LINK + DASH + FORMAT_FUNCTION + DASH + EXPRESSION
states = {} states = {}
states[BASE] = LexerState.new(patterns) states[BASE] = LexerState.new(patterns)
states[BASE].add_transition(Constants.TokenType.BeginCommand, COMMAND, true) states[BASE].add_transition(Constants.TokenType.BeginCommand, COMMAND, true)
states[BASE].add_transition(Constants.TokenType.ExpressionFunctionStart, inline_expression, true)
states[BASE].add_transition(Constants.TokenType.FormatFunctionStart, FORMAT_FUNCTION, true)
states[BASE].add_transition(Constants.TokenType.OptionStart, LINK, true) states[BASE].add_transition(Constants.TokenType.OptionStart, LINK, true)
states[BASE].add_transition(Constants.TokenType.ShortcutOption, shortcut_option) states[BASE].add_transition(Constants.TokenType.ShortcutOption, shortcut_option)
states[BASE].add_transition(Constants.TokenType.TagMarker, TAG, true) states[BASE].add_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}
# 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)
states[shortcut_option] = LexerState.new(patterns) states[shortcut_option] = LexerState.new(patterns)
states[shortcut_option].track_indent = true states[shortcut_option].track_indent = true
states[shortcut_option].add_transition(Constants.TokenType.BeginCommand, EXPRESSION, true) states[shortcut_option].add_transition(Constants.TokenType.BeginCommand, EXPRESSION, true)
states[shortcut_option].add_transition(Constants.TokenType.ExpressionFunctionStart, inline_expression, true)
states[shortcut_option].add_transition(Constants.TokenType.TagMarker, shortcut_option_tag, true) states[shortcut_option].add_transition(Constants.TokenType.TagMarker, shortcut_option_tag, true)
states[shortcut_option].add_text_rule(Constants.TokenType.Text, BASE) states[shortcut_option].add_text_rule(Constants.TokenType.Text, BASE)
@ -134,40 +149,42 @@ func createstates():
states[ASSIGNMENT].add_transition(Constants.TokenType.MultiplyAssign, EXPRESSION) states[ASSIGNMENT].add_transition(Constants.TokenType.MultiplyAssign, EXPRESSION)
states[ASSIGNMENT].add_transition(Constants.TokenType.DivideAssign, EXPRESSION) states[ASSIGNMENT].add_transition(Constants.TokenType.DivideAssign, EXPRESSION)
states[FORMAT_FUNCTION] = LexerState.new(patterns)
states[FORMAT_FUNCTION].add_transition(Constants.TokenType.FormatFunctionEnd, BASE, true)
states[FORMAT_FUNCTION].add_transition(Constants.TokenType.ExpressionFunctionStart, format_expression, true)
states[FORMAT_FUNCTION].add_text_rule(Constants.TokenType.Text)
states[format_expression] = LexerState.new(patterns)
states[format_expression].add_transition(Constants.TokenType.ExpressionFunctionEnd, FORMAT_FUNCTION)
form_expression_state(states[format_expression])
states[inline_expression] = LexerState.new(patterns)
states[inline_expression].add_transition(Constants.TokenType.ExpressionFunctionEnd, BASE)
form_expression_state(states[inline_expression])
states[EXPRESSION] = LexerState.new(patterns) states[EXPRESSION] = LexerState.new(patterns)
states[EXPRESSION].add_transition(Constants.TokenType.EndCommand, BASE) states[EXPRESSION].add_transition(Constants.TokenType.EndCommand, BASE)
states[EXPRESSION].add_transition(Constants.TokenType.Number) # states[EXPRESSION].add_transition(Constants.TokenType.FormatFunctionEnd, BASE)
states[EXPRESSION].add_transition(Constants.TokenType.Str) form_expression_state(states[EXPRESSION])
states[EXPRESSION].add_transition(Constants.TokenType.LeftParen)
states[EXPRESSION].add_transition(Constants.TokenType.RightParen)
states[EXPRESSION].add_transition(Constants.TokenType.EqualTo)
states[EXPRESSION].add_transition(Constants.TokenType.EqualToOrAssign)
states[EXPRESSION].add_transition(Constants.TokenType.NotEqualTo)
states[EXPRESSION].add_transition(Constants.TokenType.GreaterThanOrEqualTo)
states[EXPRESSION].add_transition(Constants.TokenType.GreaterThan)
states[EXPRESSION].add_transition(Constants.TokenType.LessThanOrEqualTo)
states[EXPRESSION].add_transition(Constants.TokenType.LessThan)
states[EXPRESSION].add_transition(Constants.TokenType.Add)
states[EXPRESSION].add_transition(Constants.TokenType.Minus)
states[EXPRESSION].add_transition(Constants.TokenType.Multiply)
states[EXPRESSION].add_transition(Constants.TokenType.Divide)
states[EXPRESSION].add_transition(Constants.TokenType.Modulo)
states[EXPRESSION].add_transition(Constants.TokenType.And)
states[EXPRESSION].add_transition(Constants.TokenType.Or)
states[EXPRESSION].add_transition(Constants.TokenType.Xor)
states[EXPRESSION].add_transition(Constants.TokenType.Not)
states[EXPRESSION].add_transition(Constants.TokenType.Variable)
states[EXPRESSION].add_transition(Constants.TokenType.Comma)
states[EXPRESSION].add_transition(Constants.TokenType.TrueToken)
states[EXPRESSION].add_transition(Constants.TokenType.FalseToken)
states[EXPRESSION].add_transition(Constants.TokenType.NullToken)
states[EXPRESSION].add_transition(Constants.TokenType.Identifier)
states[LINK] = LexerState.new(patterns) states[LINK] = LexerState.new(patterns)
states[LINK].add_transition(Constants.TokenType.OptionEnd, BASE, true) states[LINK].add_transition(Constants.TokenType.OptionEnd, BASE, true)
states[LINK].add_transition(Constants.TokenType.ExpressionFunctionStart, link_inline_expression, true)
states[LINK].add_transition(Constants.TokenType.FormatFunctionStart, link_format_expression, true)
states[LINK].add_transition(Constants.TokenType.FormatFunctionEnd, LINK, true)
states[LINK].add_transition(Constants.TokenType.OptionDelimit, link_destination, true) states[LINK].add_transition(Constants.TokenType.OptionDelimit, link_destination, true)
states[LINK].add_text_rule(Constants.TokenType.Text) states[LINK].add_text_rule(Constants.TokenType.Text)
states[link_format_expression] = LexerState.new(patterns)
states[link_format_expression].add_transition(Constants.TokenType.FormatFunctionEnd, LINK, true)
states[link_format_expression].add_transition(Constants.TokenType.ExpressionFunctionStart, link_inline_expression, true)
states[link_format_expression].add_text_rule(Constants.TokenType.Text)
states[link_inline_expression] = LexerState.new(patterns)
states[link_inline_expression].add_transition(Constants.TokenType.ExpressionFunctionEnd, LINK)
form_expression_state(states[link_inline_expression])
states[link_destination] = LexerState.new(patterns) states[link_destination] = LexerState.new(patterns)
states[link_destination].add_transition(Constants.TokenType.Identifier) states[link_destination].add_transition(Constants.TokenType.Identifier)
states[link_destination].add_transition(Constants.TokenType.OptionEnd, BASE) states[link_destination].add_transition(Constants.TokenType.OptionEnd, BASE)
@ -177,6 +194,34 @@ func createstates():
for key in states.keys(): for key in states.keys():
states[key].name = key states[key].name = key
func form_expression_state(expression_state):
expression_state.add_transition(Constants.TokenType.Number)
expression_state.add_transition(Constants.TokenType.Str)
expression_state.add_transition(Constants.TokenType.LeftParen)
expression_state.add_transition(Constants.TokenType.RightParen)
expression_state.add_transition(Constants.TokenType.EqualTo)
expression_state.add_transition(Constants.TokenType.EqualToOrAssign)
expression_state.add_transition(Constants.TokenType.NotEqualTo)
expression_state.add_transition(Constants.TokenType.GreaterThanOrEqualTo)
expression_state.add_transition(Constants.TokenType.GreaterThan)
expression_state.add_transition(Constants.TokenType.LessThanOrEqualTo)
expression_state.add_transition(Constants.TokenType.LessThan)
expression_state.add_transition(Constants.TokenType.Add)
expression_state.add_transition(Constants.TokenType.Minus)
expression_state.add_transition(Constants.TokenType.Multiply)
expression_state.add_transition(Constants.TokenType.Divide)
expression_state.add_transition(Constants.TokenType.Modulo)
expression_state.add_transition(Constants.TokenType.And)
expression_state.add_transition(Constants.TokenType.Or)
expression_state.add_transition(Constants.TokenType.Xor)
expression_state.add_transition(Constants.TokenType.Not)
expression_state.add_transition(Constants.TokenType.Variable)
expression_state.add_transition(Constants.TokenType.Comma)
expression_state.add_transition(Constants.TokenType.TrueToken)
expression_state.add_transition(Constants.TokenType.FalseToken)
expression_state.add_transition(Constants.TokenType.NullToken)
expression_state.add_transition(Constants.TokenType.Identifier)
func tokenize(): func tokenize():
var tokens = [] var tokens = []
@ -371,7 +416,7 @@ class Token:
value = _value value = _value
func _to_string(): func _to_string():
return '%s (%s) at %s:%s (state: %s)' % [Constants.token_type_name(type),value, line_number, column, lexer_state] return '%s (%s) at %s:%s (state: %s)' % [Constants.token_type_name(type), value, line_number, column, lexer_state]
class LexerState: class LexerState:
var name = '' var name = ''

View file

@ -87,8 +87,6 @@ class ParseNode:
var tokens = _parser.tokens as Array var tokens = _parser.tokens as Array
if tokens.size() > 0: if tokens.size() > 0:
line_number = tokens.front().line_number line_number = tokens.front().line_number
else:
line_number = -1
tags = [] tags = []
@ -132,6 +130,105 @@ class WolNode extends ParseNode:
class Header extends ParseNode: class Header extends ParseNode:
pass 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 !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
@ -142,7 +239,7 @@ class Statement extends ParseNode:
var assignment var assignment
var shortcut_option_group var shortcut_option_group
var custom_command var custom_command
var line = '' var line
func _init(parent, parser).(parent, parser): func _init(parent, parser).(parent, parser):
if Block.can_parse(parser): if Block.can_parse(parser):
@ -170,7 +267,7 @@ class Statement extends ParseNode:
type = Type.CustomCommand type = Type.CustomCommand
elif parser.next_symbol_is([Constants.TokenType.Text]): elif parser.next_symbol_is([Constants.TokenType.Text]):
line = parser.expect_symbol([Constants.TokenType.Text]).value line = LineNode.new(self, parser)
type = Type.Line type = Type.Line
else: else:
@ -203,7 +300,7 @@ class Statement extends ParseNode:
Type.CustomCommand: Type.CustomCommand:
info.append(custom_command.tree_string(indent_level)) info.append(custom_command.tree_string(indent_level))
Type.Line: Type.Line:
info.append(tab(indent_level, 'Line: %s' % line)) info.append(line.tree_string(indent_level))
_: _:
printerr('Cannot print statement') printerr('Cannot print statement')
@ -233,11 +330,14 @@ class CustomCommand extends ParseNode:
#if first token is identifier and second is leftt parenthesis #if first token is identifier and second is leftt parenthesis
#evaluate as function #evaluate as function
if (command_tokens.size() > 1 && command_tokens[0].type == Constants.TokenType.Identifier if command_tokens.size() > 1 \
&& command_tokens[1].type == Constants.TokenType.LeftParen): and command_tokens[0].type == Constants.TokenType.Identifier \
and command_tokens[1].type == Constants.TokenType.LeftParen:
var p = get_script().new(command_tokens, parser.library) var p = get_script().new(command_tokens, parser.library)
expression = ExpressionNode.parse(self, p) expression = ExpressionNode.parse(self, p)
type = Type.Expression type = Type.Expression
else: else:
#otherwise evaluuate command #otherwise evaluuate command
type = Type.ClientCommand type = Type.ClientCommand
@ -545,10 +645,10 @@ class ExpressionNode extends ParseNode:
var parameters = [] var parameters = []
func _init(parent, parser, _value, _function = '', _parameters = []).(parent, parser): func _init(parent, parser, _value, _function = '', _parameters = []).(parent, parser):
if _value != null: if _value != null:
type = Constants.ExpressionType.Value type = Constants.ExpressionType.Value
value = _value value = _value
else: else:
type = Constants.ExpressionType.FunctionCall type = Constants.ExpressionType.FunctionCall
function = _function function = _function
@ -568,7 +668,7 @@ class ExpressionNode extends ParseNode:
return info.join('') return info.join('')
# 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
static func parse(parent, parser): static func parse(parent, parser):
var rpn = [] var rpn = []
@ -595,7 +695,7 @@ class ExpressionNode extends ParseNode:
var last var last
#read expression content #read expression content
while parser.tokens.size() > 0 && parser.next_symbol_is(valid_types): while parser.tokens.size() > 0 and parser.next_symbol_is(valid_types):
var next = parser.expect_symbol(valid_types) var next = parser.expect_symbol(valid_types)
if next.type == Constants.TokenType.Variable \ if next.type == Constants.TokenType.Variable \
@ -633,7 +733,7 @@ class ExpressionNode extends ParseNode:
#find the closest function on stack #find the closest function on stack
#increment parameters #increment parameters
func_stack.back().param_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
@ -685,7 +785,7 @@ class ExpressionNode extends ParseNode:
#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().param_count+=1 func_stack.back().parameter_count+=1
rpn.append(op_stack.pop_back()) rpn.append(op_stack.pop_back())
func_stack.pop_back() func_stack.pop_back()
@ -730,7 +830,7 @@ class ExpressionNode extends ParseNode:
var function_name = next.value var function_name = next.value
var function_parameters = [] var function_parameters = []
for _i in range(next.param_count): for _i in range(next.parameter_count):
function_parameters.append(eval_stack.pop_back()) function_parameters.append(eval_stack.pop_back())
function_parameters.invert() function_parameters.invert()
@ -802,7 +902,7 @@ class Assignment extends ParseNode:
info.append(tab(indent_level + 1, destination)) info.append(tab(indent_level + 1, destination))
info.append(tab(indent_level + 1, Constants.token_type_name(operation))) info.append(tab(indent_level + 1, Constants.token_type_name(operation)))
info.append(value.tree_string(indent_level + 1)) info.append(value.tree_string(indent_level + 1))
return info.join('') return PoolStringArray(info).join('')
static func can_parse(parser): static func can_parse(parser):

View file

@ -76,6 +76,12 @@ enum TokenType {
#8 Command syntax ('<<foo>>') #8 Command syntax ('<<foo>>')
BeginCommand, EndCommand, BeginCommand, EndCommand,
ExpressionFunctionStart, # {
ExpressionFunctionEnd, # }
FormatFunctionStart, # [
FormatFunctionEnd, # ]
#10 Variables ('$foo') #10 Variables ('$foo')
Variable, Variable,
@ -145,7 +151,8 @@ enum TokenType {
} }
enum ExpressionType { enum ExpressionType {
Value, FunctionCall Value,
FunctionCall
} }
enum StatementTypes { enum StatementTypes {
@ -155,7 +162,8 @@ enum StatementTypes {
IfStatement, IfStatement,
OptionStatement, OptionStatement,
AssignmentStatement, AssignmentStatement,
Line Line,
InlineExpression
} }
enum ValueType { enum ValueType {

View file

@ -15,6 +15,7 @@ func get_function(name):
func import_library(other): func import_library(other):
Constants.merge_dir(functions, other.functions) Constants.merge_dir(functions, other.functions)
other.virtual_machine = virtual_machine
func register_function(name, parameter_count, function, returns_value): func register_function(name, parameter_count, function, returns_value):
var functionInfo = FunctionInfo.new(name, parameter_count, function, returns_value) var functionInfo = FunctionInfo.new(name, parameter_count, function, returns_value)

View file

@ -3,6 +3,7 @@ extends Object
const Constants = preload('res://addons/Wol/core/Constants.gd') const Constants = preload('res://addons/Wol/core/Constants.gd')
var name = '' var name = ''
var filename = ''
var strings = {} var strings = {}
var nodes = {} var nodes = {}
@ -12,6 +13,7 @@ class Line:
var line_number = -1 var line_number = -1
var file_name = '' var file_name = ''
var implicit = false var implicit = false
var substitutions = []
var meta = [] var meta = []
func _init(_text, _node_name, _line_number, _file_name, _implicit, _meta): func _init(_text, _node_name, _line_number, _file_name, _implicit, _meta):
@ -21,6 +23,13 @@ class Line:
implicit = _implicit implicit = _implicit
meta = _meta meta = _meta
func clone():
return get_script().new(text, node_name, line_number, file_name, implicit, meta)
func _to_string():
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
@ -31,6 +40,9 @@ class Option:
id = _id id = _id
destination = _destination destination = _destination
func clone():
return get_script().new(self)
class Command: class Command:
var command = '' var command = ''

View file

@ -2,9 +2,6 @@ extends Object
const Constants = preload('res://addons/Wol/core/Constants.gd') const Constants = preload('res://addons/Wol/core/Constants.gd')
const NULL_STRING = 'null'
const FALSE_STRING = 'false'
const TRUE_STRING = 'true'
const NANI = 'NaN' const NANI = 'NaN'
var type = Constants.ValueType.Nullean var type = Constants.ValueType.Nullean
@ -74,7 +71,7 @@ func set_value(value):
func add(other): func add(other):
if type == Constants.ValueType.Str or other.type == Constants.ValueType.Str: if type == Constants.ValueType.Str or other.type == Constants.ValueType.Str:
return get_script().new('%s%s'%[value(),other.value()]) return get_script().new('%s%s' % [value(), other.value()])
if type == Constants.ValueType.Number and other.type == Constants.ValueType.Number: if type == Constants.ValueType.Number and other.type == Constants.ValueType.Number:
return get_script().new(number + other.number) return get_script().new(number + other.number)
return null return null

View file

@ -7,24 +7,53 @@ position: 0, 0
<<command_with multiple arguments>> <<command_with multiple arguments>>
// remove "to" to trigger error // remove "to" to trigger error
<<set $direction to 'this'>> <<set $direction to 'that'>>
<<set $one to 1>> <<set $one to 1>>
// Implement inline expressions // Implement inline expressions
Bob: Theresa, {$direction} way! #line:5d7a7c <<if visit_count() == 1>>
Theresa: Did you know one + one equals {$one + $one}? Narrator: You, {$direction} way!
Bob: You wanna go somewhere? <<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]] -> Go to the store
[[Lets stay here and talk|Talk]] [[TheStore]]
// -> Wait, how many times have I been to the store?
// Narrator: You've been to the store {visit_count('TheStore')} times.
// [[Start]]
-> Lets stay here and talk
[[Talk]]
=== ===
title: TheStore title: TheStore
tags: tags:
colorID: 0 colorID: 0
position: 0, 200 position: 0, 200
--- ---
Clerk: Welcome to the store. Guy: Hey what's up I need your help can you come here?
Clerk: Can I help you with anything? You: Well I can't I'm buying clothes.
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?!
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?
You: There's just more soup.
Guy: Then go to the next aisle!
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?!
You: I mean I'm at soup.
Guy: WHAT STORE ARE YOU IN?!
You: IM AT THE SOUP STORE!!
Guy: WHY ARE YOU BUYING CLOTHES AT THE SOUP STORE?!
You: FUCK YOU!
[[Go home|Start]] [[Go home|Start]]
=== ===
title: Talk title: Talk
@ -32,9 +61,9 @@ tags:
colorID: 0 colorID: 0
position: 0, 400 position: 0, 400
--- ---
Bob: So how are you really? Narrator: So how are you really?
Theresa: I'm good! You: I'm good!
Bob: Do you want to continue talking? Narrator: Do you want to continue talking?
-> Yes -> Yes
[[Start]] [[Start]]
-> No -> No

View file

@ -9,30 +9,12 @@
config_version=4 config_version=4
_global_script_classes=[ { _global_script_classes=[ {
"base": "Object",
"class": "Compiler",
"language": "GDScript",
"path": "res://addons/Wol/core/compiler/Compiler.gd"
}, {
"base": "Object",
"class": "Lexer",
"language": "GDScript",
"path": "res://addons/Wol/core/compiler/Lexer.gd"
}, {
"base": "Object",
"class": "Program",
"language": "GDScript",
"path": "res://addons/Wol/core/Program.gd"
}, {
"base": "Node", "base": "Node",
"class": "Wol", "class": "Wol",
"language": "GDScript", "language": "GDScript",
"path": "res://addons/Wol/Wol.gd" "path": "res://addons/Wol/Wol.gd"
} ] } ]
_global_script_class_icons={ _global_script_class_icons={
"Compiler": "",
"Lexer": "",
"Program": "",
"Wol": "" "Wol": ""
} }