fully cleaned up the compiler

This commit is contained in:
Bram Dingelstad 2021-11-21 14:44:13 +01:00
parent 13d2391e57
commit 83c4808d5a
4 changed files with 264 additions and 363 deletions

View file

@ -83,7 +83,6 @@ func _on_dialogue_finished():
func _on_node_start(node): func _on_node_start(node):
emit_signal('node_started', node) emit_signal('node_started', node)
resume()
func _on_node_finished(node): func _on_node_finished(node):
emit_signal('node_finished', node) emit_signal('node_finished', node)

View file

@ -8,21 +8,15 @@ const Parser = preload('res://addons/Wol/core/compiler/parser.gd')
const INVALID_TITLE = '[\\[<>\\]{}\\|:\\s#\\$]' const INVALID_TITLE = '[\\[<>\\]{}\\|:\\s#\\$]'
const NO_ERROR = 0x00
const LEXER_FAILURE = 0x01
const PARSER_FAILURE = 0x02
const INVALID_HEADER = 0x04
const ERR_COMPILATION_FAILED = 0x10
var source = '' var source = ''
var filename = '' var filename = ''
var _current_node var current_node
var _contains_implicit_string_tags = false var has_implicit_string_tags = false
var _label_count = 0
var _string_table = {} var string_count = 0
var _string_count = 0 var string_table = {}
var label_count = 0
func _init(_filename, _source = null): func _init(_filename, _source = null):
filename = _filename filename = _filename
@ -50,7 +44,6 @@ func compile():
source_lines[i] = source_lines[i].strip_edges(false, true) source_lines[i] = source_lines[i].strip_edges(false, true)
var parsed_nodes = [] var parsed_nodes = []
while line_number < source_lines.size(): while line_number < source_lines.size():
var title = '' var title = ''
var body = '' var body = ''
@ -77,8 +70,6 @@ func compile():
if line_number >= source_lines.size() or line == '---': if line_number >= source_lines.size() or line == '---':
break break
line_number += 1
# past header # past header
var body_lines = [] var body_lines = []
@ -108,8 +99,8 @@ func compile():
for node in parsed_nodes: for node in parsed_nodes:
compile_node(program, node) compile_node(program, node)
for key in _string_table: for key in string_table:
program.strings[key] = _string_table[key] program.strings[key] = string_table[key]
return program return program
@ -131,7 +122,7 @@ func compile_node(program, parsed_node):
) )
else: else:
var start_label = register_label() var start_label = register_label()
emit(Constants.ByteCode.Label,node_compiled,[Program.Operand.new(start_label)]) emit(Constants.ByteCode.Label, node_compiled, [Program.Operand.new(start_label)])
for statement in parsed_node.statements: for statement in parsed_node.statements:
generate_statement(node_compiled, statement) generate_statement(node_compiled, statement)
@ -151,110 +142,102 @@ func compile_node(program, parsed_node):
program.nodes[node_compiled.name] = node_compiled program.nodes[node_compiled.name] = node_compiled
func register_string(text:String,node_name:String,id:String='',line_number:int=-1,tags:Array=[])->String: func register_string(text, node_name, id = '', line_number = -1, tags = []):
var line_id_used : String var line_id_used = ''
var implicit = false
var implicit : bool
if id.empty(): if id.empty():
line_id_used = '%s-%s-%d' % [self.filename,node_name,self._string_count] line_id_used = '%s-%s-%d' % [filename, node_name, string_count]
self._string_count+=1 string_count += 1
#use this when we generate implicit tags #use this when we generate implicit tags
#they are not saved and are generated #they are not saved and are generated
#aka dummy tags that change on each compilation #aka dummy tags that change on each compilation
_contains_implicit_string_tags = true has_implicit_string_tags = true
implicit = true implicit = true
else : else :
line_id_used = id line_id_used = id
implicit = false implicit = false
var string_info = Program.Line.new(text,node_name,line_number,filename,implicit,tags) var string_info = Program.Line.new(text, node_name, line_number, filename, implicit, tags)
#add to string table and return id string_table[line_id_used] = string_info
self._string_table[line_id_used] = string_info
return line_id_used return line_id_used
func register_label(comment:String='')->String: func register_label(comment = ''):
_label_count+=1 label_count += 1
return 'L%s%s' %[ _label_count , comment] return 'Label%s%s' % [label_count, comment]
func emit(bytecode, node = _current_node, operands = []): func emit(bytecode, node = current_node, operands = []):
var instruction = Program.Instruction.new(null) var instruction = Program.Instruction.new(null)
instruction.operation = bytecode instruction.operation = bytecode
instruction.operands = operands instruction.operands = operands
if node == null: if node == null:
printerr('trying to emit to null node with byte_code: %s' % bytecode) printerr('Trying to emit to null node with byte_code: %s' % bytecode)
return return
node.instructions.append(instruction) node.instructions.append(instruction)
if bytecode == Constants.ByteCode.Label: if bytecode == Constants.ByteCode.Label:
#add to label table node.labels[instruction.operands[0].value] = node.instructions.size() - 1
node.labels[instruction.operands[0].value] = node.instructions.size()-1
func generate_header():
pass
func generate_statement(node,statement):
func generate_statement(node, statement):
match statement.type: match statement.type:
Constants.StatementTypes.CustomCommand: Constants.StatementTypes.CustomCommand:
generate_custom_command(node,statement.custom_command) generate_custom_command(node, statement.custom_command)
Constants.StatementTypes.ShortcutOptionGroup: Constants.StatementTypes.ShortcutOptionGroup:
generate_shortcut_group(node,statement.shortcut_option_group) generate_shortcut_group(node, statement.shortcut_option_group)
Constants.StatementTypes.Block: Constants.StatementTypes.Block:
generate_block(node,statement.block.statements) generate_block(node, statement.block.statements)
Constants.StatementTypes.IfStatement: Constants.StatementTypes.IfStatement:
generate_if(node,statement.if_statement) generate_if(node, statement.if_statement)
Constants.StatementTypes.OptionStatement: Constants.StatementTypes.OptionStatement:
generate_option(node,statement.option_statement) generate_option(node, statement.option_statement)
Constants.StatementTypes.AssignmentStatement: Constants.StatementTypes.AssignmentStatement:
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,statement.line)
_: _:
assert(false, 'Illegal statement type [%s]. Could not generate code.' % statement.type) assert(false, 'Illegal statement type [%s]. Could not generate code.' % statement.type)
#compile instructions for custom commands func generate_custom_command(node, command):
func generate_custom_command(node,command): # TODO: See if the first tree of this statement is being used
#print('generating custom command')
#can evaluate command
if command.expression != null: if command.expression != null:
generate_expression(node,command.expression) generate_expression(node, command.expression)
else: else:
var command_string = command.client_command var command_string = command.client_command
if command_string == 'stop': if command_string == 'stop':
emit(Constants.ByteCode.Stop,node) emit(Constants.ByteCode.Stop, node)
else : else :
emit(Constants.ByteCode.RunCommand,node,[Program.Operand.new(command_string)]) emit(Constants.ByteCode.RunCommand, node, [Program.Operand.new(command_string)])
#compile instructions for linetags and use them func generate_line(node, statement, line):
# \#line:number # TODO: Implement proper line numbers (global and local)
func generate_line(node,statement,line:String): var num = register_string(line, node.name, '', statement.line_number, []);
var num : String = register_string(line, node.name, '', statement.line_number, []);
emit(Constants.ByteCode.RunLine, node, [Program.Operand.new(num)]) emit(Constants.ByteCode.RunLine, node, [Program.Operand.new(num)])
func generate_shortcut_group(node,shortcut_group): func generate_shortcut_group(node,shortcut_group):
# print('generating shortcutoptopn group') var end = register_label('group_end')
var end : String = register_label('group_end') var labels = []
var option_count = 0
var labels : Array = []#String
var option_count : int = 0
for option in shortcut_group.options: for option in shortcut_group.options:
var op_destination : String = register_label('option_%s'%[option_count+1]) var endof_clause = ''
var op_destination = register_label('option_%s' % [option_count + 1])
labels.append(op_destination) labels.append(op_destination)
var endof_clause : String = '' if option.condition != null:
endof_clause = register_label('conditional_%s' % option_count)
if option.condition != null : generate_expression(node, option.condition)
endof_clause = register_label('conditional_%s'%option_count)
generate_expression(node,option.condition)
emit(Constants.ByteCode.JumpIfFalse, node, [Program.Operand.new(endof_clause)]) emit(Constants.ByteCode.JumpIfFalse, node, [Program.Operand.new(endof_clause)])
var label_line_id = '' #TODO: Add tag support var label_line_id = '' #TODO: Add tag support
@ -265,168 +248,153 @@ func generate_shortcut_group(node,shortcut_group):
[] []
) )
emit(Constants.ByteCode.AddOption,node,[Program.Operand.new(label_string_id),Program.Operand.new(op_destination)]) emit(Constants.ByteCode.AddOption, node, [Program.Operand.new(label_string_id), Program.Operand.new(op_destination)])
if option.condition != null : if option.condition != null:
emit(Constants.ByteCode.Label,node,[Program.Operand.new(endof_clause)]) emit(Constants.ByteCode.Label, node, [Program.Operand.new(endof_clause)])
emit(Constants.ByteCode.Pop,node) emit(Constants.ByteCode.Pop, node)
option_count+=1 option_count += 1
emit(Constants.ByteCode.ShowOptions,node) emit(Constants.ByteCode.ShowOptions, node)
emit(Constants.ByteCode.Jump,node) emit(Constants.ByteCode.Jump, node)
option_count = 0 option_count = 0
for option in shortcut_group.options: for option in shortcut_group.options:
emit(Constants.ByteCode.Label,node,[Program.Operand.new(labels[option_count])]) emit(Constants.ByteCode.Label, node, [Program.Operand.new(labels[option_count])])
if option.node != null : if option.node != null:
generate_block(node,option.node.statements) generate_block(node, option.node.statements)
emit(Constants.ByteCode.JumpTo,node,[Program.Operand.new(end)]) emit(Constants.ByteCode.JumpTo, node, [Program.Operand.new(end)])
option_count+=1 option_count += 1
#end of option group emit(Constants.ByteCode.Label, node, [Program.Operand.new(end)])
emit(Constants.ByteCode.Label,node,[Program.Operand.new(end)]) emit(Constants.ByteCode.Pop, node)
#clean up
emit(Constants.ByteCode.Pop,node)
func generate_block(node, statements = []):
#compile instructions for block if not statements.empty():
#blocks are just groups of statements
func generate_block(node,statements:Array=[]):
# print('generating block')
if !statements.empty():
for statement in statements: for statement in statements:
generate_statement(node,statement) generate_statement(node, statement)
#compile if branching instructions func generate_if(node, if_statement):
func generate_if(node,if_statement): var endif = register_label('endif')
# print('generating if')
#jump to label @ end of every clause
var endif : String = register_label('endif')
for clause in if_statement.clauses: for clause in if_statement.clauses:
var end_clause : String = register_label('skip_clause') var end_clause = register_label('skip_clause')
if clause.expression!=null: if clause.expression != null:
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:
emit(Constants.ByteCode.Label,node,[Program.Operand.new(end_clause)]) emit(Constants.ByteCode.Label, node, [Program.Operand.new(end_clause)])
if clause.expression!=null: if clause.expression != null:
emit(Constants.ByteCode.Pop) emit(Constants.ByteCode.Pop)
emit(Constants.ByteCode.Label,node,[Program.Operand.new(endif)]) emit(Constants.ByteCode.Label, node, [Program.Operand.new(endif)])
func generate_option(node, option):
#compile instructions for options var destination = option.destination
func generate_option(node,option):
# print('generating option')
var destination : String = option.destination
if option.label == null or option.label.empty(): if option.label == null or option.label.empty():
#jump to another node emit(Constants.ByteCode.RunNode, node, [Program.Operand.new(destination)])
emit(Constants.ByteCode.RunNode,node,[Program.Operand.new(destination)])
else : else :
var line_iD : String = ''#tags not supported TODO: ADD TAG SUPPORT var line_id = '' #TODO: ADD TAG SUPPORT
var string_iD = register_string(option.label,node.name,line_iD,option.line_number,[]) var string_id = register_string(option.label, node.name, line_id, option.line_number, [])
emit(Constants.ByteCode.AddOption,node,[Program.Operand.new(string_iD),Program.Operand.new(destination)]) emit(Constants.ByteCode.AddOption, node, [Program.Operand.new(string_id), Program.Operand.new(destination)])
func generate_assignment(node, assignment):
#compile instructions for assigning values
func generate_assignment(node,assignment):
# print('generating assign')
#assignment
if assignment.operation == Constants.TokenType.EqualToOrAssign: if assignment.operation == Constants.TokenType.EqualToOrAssign:
#evaluate the expression to a value for the stack generate_expression(node, assignment.value)
generate_expression(node,assignment.value)
else : else :
#this is combined op emit(Constants.ByteCode.PushVariable, node, [assignment.destination])
#get value of var generate_expression(node, assignment.value)
emit(Constants.ByteCode.PushVariable,node,[assignment.destination])
#evaluate the expression and push value to stack
generate_expression(node,assignment.value)
#stack contains oldvalue and result
match assignment.operation: match assignment.operation:
Constants.TokenType.AddAssign: Constants.TokenType.AddAssign:
emit(Constants.ByteCode.CallFunc,node, emit(
[Program.Operand.new(Constants.token_type_name(Constants.TokenType.Add))]) Constants.ByteCode.CallFunc,
node,
[Program.Operand.new(Constants.token_type_name(Constants.TokenType.Add))]
)
Constants.TokenType.MinusAssign: Constants.TokenType.MinusAssign:
emit(Constants.ByteCode.CallFunc,node, emit(
[Program.Operand.new(Constants.token_type_name(Constants.TokenType.Minus))]) Constants.ByteCode.CallFunc,
node,
[Program.Operand.new(Constants.token_type_name(Constants.TokenType.Minus))]
)
Constants.TokenType.MultiplyAssign: Constants.TokenType.MultiplyAssign:
emit(Constants.ByteCode.CallFunc,node, emit(
[Program.Operand.new(Constants.token_type_name(Constants.TokenType.MultiplyAssign))]) Constants.ByteCode.CallFunc,
node,
[Program.Operand.new(Constants.token_type_name(Constants.TokenType.MultiplyAssign))]
)
Constants.TokenType.DivideAssign: Constants.TokenType.DivideAssign:
emit(Constants.ByteCode.CallFunc,node, emit(
[Program.Operand.new(Constants.token_type_name(Constants.TokenType.DivideAssign))]) Constants.ByteCode.CallFunc,
node,
[Program.Operand.new(Constants.token_type_name(Constants.TokenType.DivideAssign))]
)
_: _:
printerr('Unable to generate assignment') printerr('Unable to generate assignment')
#stack contains destination value emit(Constants.ByteCode.StoreVariable, node, [Program.Operand.new(assignment.destination)])
#store the top of the stack in variable emit(Constants.ByteCode.Pop, node)
emit(Constants.ByteCode.StoreVariable,node,[Program.Operand.new(assignment.destination)])
#clean stack func generate_expression(node, expression):
emit(Constants.ByteCode.Pop,node)
#compile expression instructions
func generate_expression(node,expression):
# print('generating expression')
#expression = value or func call
match expression.type: match expression.type:
Constants.ExpressionType.Value: Constants.ExpressionType.Value:
generate_value(node,expression.value) generate_value(node, expression.value)
Constants.ExpressionType.FunctionCall: Constants.ExpressionType.FunctionCall:
#eval all parameters for parameter in expression.parameters:
for param in expression.params: generate_expression(node, parameter)
generate_expression(node,param)
#put the num of of params to stack emit(Constants.ByteCode.PushNumber, node, [Program.Operand.new(expression.parameters.size())])
emit(Constants.ByteCode.PushNumber,node,[Program.Operand.new(expression.params.size())]) emit(Constants.ByteCode.CallFunc, node, [Program.Operand.new(expression.function)])
#call function
emit(Constants.ByteCode.CallFunc,node,[Program.Operand.new(expression.function)])
_: _:
printerr('no expression') printerr('No expression.')
#compile value instructions func generate_value(node, value):
func generate_value(node,value):
# print('generating value')
#push value to stack
match value.value.type: match value.value.type:
Constants.ValueType.Number: Constants.ValueType.Number:
emit(Constants.ByteCode.PushNumber,node,[Program.Operand.new(value.value.as_number())]) emit(
Constants.ByteCode.PushNumber,
node,
[Program.Operand.new(value.value.as_number())]
)
Constants.ValueType.Str: Constants.ValueType.Str:
var id = register_string(value.value.as_string(), var id = register_string(
node.name,'',value.line_number,[]) value.value.as_string(),
emit(Constants.ByteCode.PushString,node,[Program.Operand.new(id)]) node.name,
'',
value.line_number,
[]
)
emit(
Constants.ByteCode.PushString,
node,
[Program.Operand.new(id)]
)
Constants.ValueType.Boolean: Constants.ValueType.Boolean:
emit(Constants.ByteCode.PushBool,node,[Program.Operand.new(value.value.as_bool())]) emit(
Constants.ByteCode.PushBool,
node,
[Program.Operand.new(value.value.as_bool())]
)
Constants.ValueType.Variable: Constants.ValueType.Variable:
emit(Constants.ByteCode.PushVariable,node,[Program.Operand.new(value.value.variable)]) emit(
Constants.ByteCode.PushVariable,
node,
[Program.Operand.new(value.value.variable)]
)
Constants.ValueType.Nullean: Constants.ValueType.Nullean:
emit(Constants.ByteCode.PushNull,node) emit(Constants.ByteCode.PushNull, node)
_: _:
printerr('Unrecognized valuenode type: %s' % value.value.type) printerr('Unrecognized valuenode type: %s' % value.value.type)
static func print_tokens(tokens:Array=[]):
var list : PoolStringArray = []
list.append('\n')
for token in tokens:
list.append('%s (%s line %s)\n'%[Constants.token_type_name(token.type),token.value,token.line_number])
print('TOKENS:')
print(list.join(''))

View file

@ -3,30 +3,29 @@ class_name Lexer
const Constants = preload('res://addons/Wol/core/constants.gd') const Constants = preload('res://addons/Wol/core/constants.gd')
const LINE_COMENT : String = '//' const LINE_COMENT = '//'
const FORWARD_SLASH : String = '/' const FORWARD_SLASH = '/'
const LINE_SEPARATOR = '\n'
const LINE_SEPARATOR : String = '\n' const BASE = 'base'
const DASH = '-'
const COMMAND = 'command'
const LINK = 'link'
const SHORTCUT = 'shortcut'
const TAG = 'tag'
const EXPRESSION = 'expression'
const ASSIGNMENT = 'assignment'
const OPTION = 'option'
const OR = 'or'
const DESTINATION = 'destination'
const BASE : String = 'base' var WHITESPACE = '\\s*'
const DASH : String = '-'
const COMMAND : String = 'command'
const LINK : String = 'link'
const SHORTCUT : String = 'shortcut'
const TAG : String = 'tag'
const EXPRESSION : String = 'expression'
const ASSIGNMENT : String = 'assignment'
const OPTION : String = 'option'
const OR : String = 'or'
const DESTINATION : String = 'destination'
var WHITESPACE : String = '\\s*' var _states = {}
var _defaultState
var _currentState
var _states : Dictionary = {} var _indentStack = []
var _defaultState : LexerState
var _currentState : LexerState
var _indentStack : Array = []
var _shouldTrackIndent : bool = false var _shouldTrackIndent : bool = false
var filename = '' var filename = ''
@ -185,11 +184,11 @@ func tokenize():
_indentStack.push_front(IntBoolPair.new(0, false)) _indentStack.push_front(IntBoolPair.new(0, false))
_shouldTrackIndent = false _shouldTrackIndent = false
var tokens : Array = [] var tokens = []
_currentState = _defaultState _currentState = _defaultState
var lines : PoolStringArray = text.split(LINE_SEPARATOR) var lines = text.split(LINE_SEPARATOR)
lines.append('') lines.append('')
var line_number : int = 1 var line_number : int = 1

View file

@ -1,11 +1,7 @@
extends Node extends Node
const Constants = preload('res://addons/Wol/core/constants.gd') const Constants = preload('res://addons/Wol/core/constants.gd')
var Value = load('res://addons/Wol/core/value.gd') const Value = preload('res://addons/Wol/core/value.gd')
const EXECUTION_COMPLETE : String = 'execution_complete_command'
var NULL_VALUE = Value.new(null)
# Function references to handlers # Function references to handlers
var line_handler var line_handler
@ -18,9 +14,9 @@ var dialogue_finished_handler
var dialogue var dialogue
var libraries var libraries
var program var program
var _state var state
var _currentNode var current_node
var execution_state = Constants.ExecutionState.Stopped var execution_state = Constants.ExecutionState.Stopped
@ -43,192 +39,155 @@ func _init(_dialogue, _libraries):
assert(command_handler.is_valid(), 'Cannot run without a command handler (_on_command)') assert(command_handler.is_valid(), 'Cannot run without a command handler (_on_command)')
assert(node_start_handler.is_valid(), 'Cannot run without a node start handler (_on_node_start)') assert(node_start_handler.is_valid(), 'Cannot run without a node start handler (_on_node_start)')
assert(node_finished_handler.is_valid(), 'Cannot run without a node finished handler (_on_node_finished)') assert(node_finished_handler.is_valid(), 'Cannot run without a node finished handler (_on_node_finished)')
assert(dialogue_finished_handler.is_valid(), 'Cannot run without a dialogue finished handler (_on_dialogue_finished)')
_state = VmState.new() state = VmState.new()
#set the node to run #set the node to run
#return true if successeful false if no node #return true if successeful false if no node
#of that name found #of that name found
func set_node(name): func set_node(name):
if program == null || program.nodes.size() == 0: if program == null or program.nodes.size() == 0:
printerr('Could not load %s : no nodes loaded' % name) printerr('Could not load %s : no nodes loaded' % name)
return false return false
if !program.nodes.has(name): if not program.nodes.has(name):
execution_state = Constants.ExecutionState.Stopped execution_state = Constants.ExecutionState.Stopped
reset() reset()
printerr('No node named %s has been loaded' % name) printerr('No node named %s has been found in dialogue, so not loading' % name)
return false return false
_currentNode = program.nodes[name] current_node = program.nodes[name]
reset() reset()
_state.currentNodeName = name state.current_node_name = name
node_start_handler.call_func(name) node_start_handler.call_func(name)
return true return true
func current_node_name()->String:
return _currentNode.nodeName
func current_node():
return _currentNode
func pause(): func pause():
execution_state = Constants.ExecutionState.Suspended execution_state = Constants.ExecutionState.Suspended
#stop exectuion
func stop(): func stop():
execution_state = Constants.ExecutionState.Stopped execution_state = Constants.ExecutionState.Stopped
reset() reset()
_currentNode = null current_node = null
#set the currently selected option and
#resume execution if waiting for result
#return false if error
func set_selected_option(id): func set_selected_option(id):
if execution_state != Constants.ExecutionState.WaitingForOption: if execution_state != Constants.ExecutionState.WaitingForOption:
printerr('Unable to select option when dialogue not waiting for option') printerr('Unable to select option when dialogue not waiting for option')
return false return false
if id < 0 or id >= _state.currentOptions.size(): if id < 0 or id >= state.current_options.size():
printerr('%d is not a valid option ' % id) printerr('%d is not a valid option!' % id)
return false return false
var destination = _state.currentOptions[id].value var destination = state.current_options[id][1]
_state.push_value(destination) state.push_value(destination)
_state.currentOptions.clear() state.current_options.clear()
#no longer waiting for option
execution_state = Constants.ExecutionState.Suspended execution_state = Constants.ExecutionState.Suspended
return true return true
func reset(): func reset():
_state = VmState.new() state = VmState.new()
func get_next_instruction(): func get_next_instruction():
return null if _currentNode.instructions.size() - 1 <= _state.programCounter else _currentNode.instructions[_state.programCounter + 1] if current_node.instructions.size() - 1 > state.programCounter:
return current_node.instructions[state.programCounter + 1]
return
func resume(): func resume():
if _currentNode == null: if current_node == null:
printerr('Cannot run dialogue with no node selected') printerr('Cannot run dialogue with no node selected')
return false return false
if execution_state == Constants.ExecutionState.WaitingForOption: if execution_state == Constants.ExecutionState.WaitingForOption:
printerr('Cannot run while waiting for option') printerr('Cannot run while waiting for option')
return false return false
execution_state = Constants.ExecutionState.Running execution_state = Constants.ExecutionState.Running
#execute instruction until something cool happens #execute instruction until something cool happens
while execution_state == Constants.ExecutionState.Running: while execution_state == Constants.ExecutionState.Running:
var currentInstruction = _currentNode.instructions[_state.programCounter] var current_instruction = current_node.instructions[state.programCounter]
run_instruction(current_instruction)
state.programCounter += 1
run_instruction(currentInstruction) if state.programCounter >= current_node.instructions.size():
_state.programCounter += 1 node_finished_handler.call_func(current_node.nodeName)
if _state.programCounter >= _currentNode.instructions.size():
node_finished_handler.call_func(_currentNode.nodeName)
execution_state = Constants.ExecutionState.Stopped execution_state = Constants.ExecutionState.Stopped
reset() reset()
dialogue_finished_handler.call_func() dialogue_finished_handler.call_func()
return true return true
func find_label_instruction(label:String)->int: func find_label_instruction(label):
if !_currentNode.labels.has(label): if not current_node.labels.has(label):
printerr('Unknown label:'+label) printerr('Unknown label:' + label)
return -1 return -1
return _currentNode.labels[label] return current_node.labels[label]
func run_instruction(instruction)->bool: func run_instruction(instruction):
match instruction.operation: match instruction.operation:
Constants.ByteCode.Label: Constants.ByteCode.Label:
pass pass
# Jump to named label
Constants.ByteCode.JumpTo: Constants.ByteCode.JumpTo:
#jump to named label state.programCounter = find_label_instruction(instruction.operands[0].value) - 1
_state .programCounter = find_label_instruction(instruction.operands[0].value)-1
Constants.ByteCode.RunLine: Constants.ByteCode.RunLine:
#look up string from string table # Lookup string and give back as line
#pass it to client as line
var key = instruction.operands[0].value var key = instruction.operands[0].value
var line = program.strings[key] var line = program.strings[key]
#the second operand is the expression count # The second operand is the expression count of format function
# of format function # TODO: Add format functions supportk
if instruction.operands.size() > 1: if instruction.operands.size() > 1:
pass#add format function support pass
var pause = line_handler.call_func(line) var return_state = line_handler.call_func(line)
if pause == Constants.HandlerState.PauseExecution: if return_state == Constants.HandlerState.PauseExecution:
execution_state = Constants.ExecutionState.Suspended execution_state = Constants.ExecutionState.Suspended
Constants.ByteCode.RunCommand: Constants.ByteCode.RunCommand:
var commandText : String = instruction.operands[0].value var command_text = instruction.operands[0].value
var command = Program.Command.new(command_text)
if instruction.operands.size() > 1: var return_state = command_handler.call_func(command)
pass#add format function if return_state == Constants.HandlerState.PauseExecution:
var command = Program.Command.new(commandText)
var pause = command_handler.call_func(command) as int
if pause == Constants.HandlerState.PauseExecution:
execution_state = Constants.ExecutionState.Suspended execution_state = Constants.ExecutionState.Suspended
Constants.ByteCode.PushString: Constants.ByteCode.PushString, \
#push String var to stack Constants.ByteCode.PushNumber, \
_state.push_value(instruction.operands[0].value)
Constants.ByteCode.PushNumber:
#push number to stack
_state.push_value(instruction.operands[0].value)
Constants.ByteCode.PushBool: Constants.ByteCode.PushBool:
#push boolean to stack state.push_value(instruction.operands[0].value)
_state.push_value(instruction.operands[0].value)
Constants.ByteCode.PushNull: Constants.ByteCode.PushNull:
#push null t state.push_value(Value.new(null))
_state.push_value(NULL_VALUE)
# Jump to named label if value of stack top is false
Constants.ByteCode.JumpIfFalse: Constants.ByteCode.JumpIfFalse:
#jump to named label if value of stack top is false if not state.peek_value().as_bool():
if !_state.peek_value().as_bool(): state.programCounter = find_label_instruction(instruction.operands[0].value) - 1
_state.programCounter = find_label_instruction(instruction.operands[0].value)-1
# Jump to label whose name is on the stack
Constants.ByteCode.Jump: Constants.ByteCode.Jump:
#jump to label whose name is on the stack var destination = state.peek_value().as_string()
var dest : String = _state.peek_value().as_string() state.programCounter = find_label_instruction(destination) - 1
_state.programCounter = find_label_instruction(dest)-1
Constants.ByteCode.Pop: Constants.ByteCode.Pop:
#pop value from stack state.pop_value()
_state.pop_value()
Constants.ByteCode.CallFunc: Constants.ByteCode.CallFunc:
#call function with params on stack var function_name = instruction.operands[0].value
#push any return value to stack var function = libraries.get_function(function_name)
var functionName : String = instruction.operands[0].value var expected_parameter_count = function.parameter_count
var actual_parameter_count = state.pop_value().as_number()
var function = libraries.get_function(functionName) if expected_parameter_count > 0 \
and expected_parameter_count != actual_parameter_count:
var expected_parameter_count : int = function.paramCount var error_data = [function_name, expected_parameter_count, actual_parameter_count]
var actual_parameter_count : int = _state.pop_value().as_number() printerr('Function "%s" expected %d parameters but got %d instead' % error_data)
#if function takes in -1 params disregard
#expect the compiler to have placed the number of params
#at the top of the stack
if expected_parameter_count == -1:
expected_parameter_count = actual_parameter_count
if expected_parameter_count != actual_parameter_count:
printerr('Function %s expected %d parameters but got %d instead' %[functionName,
expected_parameter_count,actual_parameter_count])
return false return false
var result var result
@ -236,85 +195,71 @@ func run_instruction(instruction)->bool:
if actual_parameter_count == 0: if actual_parameter_count == 0:
result = function.invoke() result = function.invoke()
else: else:
var params : Array = []#value var params = []
for _i in range(actual_parameter_count): for _i in range(actual_parameter_count):
params.push_front(_state.pop_value()) params.push_front(state.pop_value())
result = function.invoke(params) result = function.invoke(params)
if function.returnsValue: if function.returns_value:
_state.push_value(result) state.push_value(result)
Constants.ByteCode.PushVariable: Constants.ByteCode.PushVariable:
#get content of variable and push to stack var name = instruction.operands[0].value
var name : String = instruction.operands[0].value
# TODO: Reimplement variable storage
var loaded = dialogue.variable_storage.get_value(name) var loaded = dialogue.variable_storage.get_value(name)
_state.push_value(loaded) state.push_value(loaded)
Constants.ByteCode.StoreVariable: Constants.ByteCode.StoreVariable:
#store top stack value to variable var top = state.peek_value()
var top = _state.peek_value() var destination = instruction.operands[0].value
var destination : String = instruction.operands[0].value dialogue.variable_storage.set_value(destination, top)
dialogue.variable_storage.set_value(destination,top)
Constants.ByteCode.Stop: Constants.ByteCode.Stop:
#stop execution and repost it node_finished_handler.call_func(current_node.name)
node_finished_handler.call_func(_currentNode.name)
dialogue_finished_handler.call_func() dialogue_finished_handler.call_func()
execution_state = Constants.ExecutionState.Stopped execution_state = Constants.ExecutionState.Stopped
reset() reset()
Constants.ByteCode.RunNode: Constants.ByteCode.RunNode:
#run a node var name = ''
var name : String if instruction.operands.size() == 0 or instruction.operands[0].value.empty():
name = state.peek_value().value()
if (instruction.operands.size() == 0 || instruction.operands[0].value.empty()): else:
#get string from stack and jump to node with that name
name = _state.peek_value().value()
else :
name = instruction.operands[0].value name = instruction.operands[0].value
var pause = node_finished_handler.call_func(_currentNode.name) var return_state = node_finished_handler.call_func(current_node.name)
set_node(name) set_node(name)
_state.programCounter-=1 state.programCounter -= 1
if pause == 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:
# add an option to current state
var key = instruction.operands[0].value var key = instruction.operands[0].value
var line = program.strings[key] var line = program.strings[key]
# TODO: Add format functions supportk
if instruction.operands.size() > 2: if instruction.operands.size() > 2:
pass #formated text options pass
# line to show and node name state.current_options.append([line, instruction.operands[1].value])
_state.currentOptions.append(SimpleEntry.new(line, instruction.operands[1].value))
Constants.ByteCode.ShowOptions: Constants.ByteCode.ShowOptions:
#show options - stop if none if state.current_options.size() == 0:
if _state.currentOptions.size() == 0:
execution_state = Constants.ExecutionState.Stopped execution_state = Constants.ExecutionState.Stopped
reset() reset()
dialogue_finished_handler.call_func() dialogue_finished_handler.call_func()
return false return false
#present list of options var choices = []
var choices : Array = []#Option for option_index in range(state.current_options.size()):
for optionIndex in range(_state.currentOptions.size()): var option = state.current_options[option_index]
var option : SimpleEntry = _state.currentOptions[optionIndex] var line = option[0]
choices.append(Program.Option.new(option.key, optionIndex, option.value)) var destination = option[1]
choices.append(Program.Option.new(line, option_index, destination))
#we cant continue until option chosen
execution_state = Constants.ExecutionState.WaitingForOption execution_state = Constants.ExecutionState.WaitingForOption
#pass the options to the client
#delegate for them to call
#when user makes selection
options_handler.call_func(choices) options_handler.call_func(choices)
_: _:
execution_state = Constants.ExecutionState.Stopped execution_state = Constants.ExecutionState.Stopped
reset() reset()
@ -324,12 +269,10 @@ func run_instruction(instruction)->bool:
return true return true
class VmState: class VmState:
var Value = load('res://addons/Wol/core/value.gd') var current_node_name = ''
var programCounter = 0
var currentNodeName : String var current_options = []
var programCounter : int = 0 var stack = []
var currentOptions : Array = []#SimpleEntry
var stack : Array = [] #Value
func push_value(value)->void: func push_value(value)->void:
if value is Value: if value is Value:
@ -345,11 +288,3 @@ class VmState:
func clear_stack(): func clear_stack():
stack.clear() stack.clear()
class SimpleEntry:
var key
var value
func _init(_key, _value):
key = _key
value = _value