fully cleaned up the compiler
This commit is contained in:
parent
13d2391e57
commit
83c4808d5a
|
@ -83,7 +83,6 @@ func _on_dialogue_finished():
|
|||
|
||||
func _on_node_start(node):
|
||||
emit_signal('node_started', node)
|
||||
resume()
|
||||
|
||||
func _on_node_finished(node):
|
||||
emit_signal('node_finished', node)
|
||||
|
|
|
@ -8,21 +8,15 @@ const Parser = preload('res://addons/Wol/core/compiler/parser.gd')
|
|||
|
||||
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 filename = ''
|
||||
|
||||
var _current_node
|
||||
var _contains_implicit_string_tags = false
|
||||
var _label_count = 0
|
||||
var current_node
|
||||
var has_implicit_string_tags = false
|
||||
|
||||
var _string_table = {}
|
||||
var _string_count = 0
|
||||
var string_count = 0
|
||||
var string_table = {}
|
||||
var label_count = 0
|
||||
|
||||
func _init(_filename, _source = null):
|
||||
filename = _filename
|
||||
|
@ -50,7 +44,6 @@ func compile():
|
|||
source_lines[i] = source_lines[i].strip_edges(false, true)
|
||||
|
||||
var parsed_nodes = []
|
||||
|
||||
while line_number < source_lines.size():
|
||||
var title = ''
|
||||
var body = ''
|
||||
|
@ -77,8 +70,6 @@ func compile():
|
|||
if line_number >= source_lines.size() or line == '---':
|
||||
break
|
||||
|
||||
line_number += 1
|
||||
|
||||
# past header
|
||||
var body_lines = []
|
||||
|
||||
|
@ -108,8 +99,8 @@ func compile():
|
|||
for node in parsed_nodes:
|
||||
compile_node(program, node)
|
||||
|
||||
for key in _string_table:
|
||||
program.strings[key] = _string_table[key]
|
||||
for key in string_table:
|
||||
program.strings[key] = string_table[key]
|
||||
|
||||
return program
|
||||
|
||||
|
@ -131,7 +122,7 @@ func compile_node(program, parsed_node):
|
|||
)
|
||||
else:
|
||||
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:
|
||||
generate_statement(node_compiled, statement)
|
||||
|
@ -151,110 +142,102 @@ func compile_node(program, parsed_node):
|
|||
|
||||
program.nodes[node_compiled.name] = node_compiled
|
||||
|
||||
func register_string(text:String,node_name:String,id:String='',line_number:int=-1,tags:Array=[])->String:
|
||||
var line_id_used : String
|
||||
|
||||
var implicit : bool
|
||||
func register_string(text, node_name, id = '', line_number = -1, tags = []):
|
||||
var line_id_used = ''
|
||||
var implicit = false
|
||||
|
||||
if id.empty():
|
||||
line_id_used = '%s-%s-%d' % [self.filename,node_name,self._string_count]
|
||||
self._string_count+=1
|
||||
line_id_used = '%s-%s-%d' % [filename, node_name, string_count]
|
||||
string_count += 1
|
||||
|
||||
#use this when we generate implicit tags
|
||||
#they are not saved and are generated
|
||||
#aka dummy tags that change on each compilation
|
||||
_contains_implicit_string_tags = true
|
||||
has_implicit_string_tags = true
|
||||
|
||||
implicit = true
|
||||
else :
|
||||
line_id_used = id
|
||||
implicit = false
|
||||
|
||||
var string_info = Program.Line.new(text,node_name,line_number,filename,implicit,tags)
|
||||
#add to string table and return id
|
||||
self._string_table[line_id_used] = string_info
|
||||
var string_info = Program.Line.new(text, node_name, line_number, filename, implicit, tags)
|
||||
string_table[line_id_used] = string_info
|
||||
|
||||
return line_id_used
|
||||
|
||||
func register_label(comment:String='')->String:
|
||||
_label_count+=1
|
||||
return 'L%s%s' %[ _label_count , comment]
|
||||
func register_label(comment = ''):
|
||||
label_count += 1
|
||||
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)
|
||||
instruction.operation = bytecode
|
||||
instruction.operands = operands
|
||||
|
||||
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
|
||||
|
||||
node.instructions.append(instruction)
|
||||
|
||||
if bytecode == Constants.ByteCode.Label:
|
||||
#add to label table
|
||||
node.labels[instruction.operands[0].value] = node.instructions.size()-1
|
||||
|
||||
|
||||
func generate_header():
|
||||
pass
|
||||
|
||||
func generate_statement(node,statement):
|
||||
node.labels[instruction.operands[0].value] = node.instructions.size() - 1
|
||||
|
||||
func generate_statement(node, statement):
|
||||
match statement.type:
|
||||
Constants.StatementTypes.CustomCommand:
|
||||
generate_custom_command(node,statement.custom_command)
|
||||
generate_custom_command(node, statement.custom_command)
|
||||
|
||||
Constants.StatementTypes.ShortcutOptionGroup:
|
||||
generate_shortcut_group(node,statement.shortcut_option_group)
|
||||
generate_shortcut_group(node, statement.shortcut_option_group)
|
||||
|
||||
Constants.StatementTypes.Block:
|
||||
generate_block(node,statement.block.statements)
|
||||
generate_block(node, statement.block.statements)
|
||||
|
||||
Constants.StatementTypes.IfStatement:
|
||||
generate_if(node,statement.if_statement)
|
||||
generate_if(node, statement.if_statement)
|
||||
|
||||
Constants.StatementTypes.OptionStatement:
|
||||
generate_option(node,statement.option_statement)
|
||||
generate_option(node, statement.option_statement)
|
||||
|
||||
Constants.StatementTypes.AssignmentStatement:
|
||||
generate_assignment(node,statement.assignment)
|
||||
generate_assignment(node, statement.assignment)
|
||||
|
||||
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)
|
||||
|
||||
#compile instructions for custom commands
|
||||
func generate_custom_command(node,command):
|
||||
#print('generating custom command')
|
||||
#can evaluate command
|
||||
func generate_custom_command(node, command):
|
||||
# TODO: See if the first tree of this statement is being used
|
||||
if command.expression != null:
|
||||
generate_expression(node,command.expression)
|
||||
generate_expression(node, command.expression)
|
||||
else:
|
||||
var command_string = command.client_command
|
||||
if command_string == 'stop':
|
||||
emit(Constants.ByteCode.Stop,node)
|
||||
emit(Constants.ByteCode.Stop, node)
|
||||
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
|
||||
# \#line:number
|
||||
func generate_line(node,statement,line:String):
|
||||
var num : String = register_string(line, node.name, '', statement.line_number, []);
|
||||
func generate_line(node, statement, line):
|
||||
# TODO: Implement proper line numbers (global and local)
|
||||
var num = register_string(line, node.name, '', statement.line_number, []);
|
||||
emit(Constants.ByteCode.RunLine, node, [Program.Operand.new(num)])
|
||||
|
||||
func generate_shortcut_group(node,shortcut_group):
|
||||
# print('generating shortcutoptopn group')
|
||||
var end : String = register_label('group_end')
|
||||
|
||||
var labels : Array = []#String
|
||||
|
||||
var option_count : int = 0
|
||||
var end = register_label('group_end')
|
||||
var labels = []
|
||||
var option_count = 0
|
||||
|
||||
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)
|
||||
|
||||
var endof_clause : String = ''
|
||||
|
||||
if option.condition != null :
|
||||
endof_clause = register_label('conditional_%s'%option_count)
|
||||
generate_expression(node,option.condition)
|
||||
if option.condition != null:
|
||||
endof_clause = register_label('conditional_%s' % option_count)
|
||||
generate_expression(node, option.condition)
|
||||
emit(Constants.ByteCode.JumpIfFalse, node, [Program.Operand.new(endof_clause)])
|
||||
|
||||
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 :
|
||||
emit(Constants.ByteCode.Label,node,[Program.Operand.new(endof_clause)])
|
||||
emit(Constants.ByteCode.Pop,node)
|
||||
if option.condition != null:
|
||||
emit(Constants.ByteCode.Label, node, [Program.Operand.new(endof_clause)])
|
||||
emit(Constants.ByteCode.Pop, node)
|
||||
|
||||
option_count+=1
|
||||
option_count += 1
|
||||
|
||||
emit(Constants.ByteCode.ShowOptions,node)
|
||||
emit(Constants.ByteCode.Jump,node)
|
||||
emit(Constants.ByteCode.ShowOptions, node)
|
||||
emit(Constants.ByteCode.Jump, node)
|
||||
|
||||
option_count = 0
|
||||
|
||||
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 :
|
||||
generate_block(node,option.node.statements)
|
||||
emit(Constants.ByteCode.JumpTo,node,[Program.Operand.new(end)])
|
||||
option_count+=1
|
||||
if option.node != null:
|
||||
generate_block(node, option.node.statements)
|
||||
emit(Constants.ByteCode.JumpTo, node, [Program.Operand.new(end)])
|
||||
option_count += 1
|
||||
|
||||
#end of option group
|
||||
emit(Constants.ByteCode.Label,node,[Program.Operand.new(end)])
|
||||
#clean up
|
||||
emit(Constants.ByteCode.Pop,node)
|
||||
emit(Constants.ByteCode.Label, node, [Program.Operand.new(end)])
|
||||
emit(Constants.ByteCode.Pop, node)
|
||||
|
||||
|
||||
#compile instructions for block
|
||||
#blocks are just groups of statements
|
||||
func generate_block(node,statements:Array=[]):
|
||||
# print('generating block')
|
||||
if !statements.empty():
|
||||
func generate_block(node, statements = []):
|
||||
if not statements.empty():
|
||||
for statement in statements:
|
||||
generate_statement(node,statement)
|
||||
generate_statement(node, statement)
|
||||
|
||||
|
||||
#compile if branching instructions
|
||||
func generate_if(node,if_statement):
|
||||
# print('generating if')
|
||||
#jump to label @ end of every clause
|
||||
var endif : String = register_label('endif')
|
||||
func generate_if(node, if_statement):
|
||||
var endif = register_label('endif')
|
||||
|
||||
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:
|
||||
generate_expression(node,clause.expression)
|
||||
emit(Constants.ByteCode.JumpIfFalse,node,[Program.Operand.new(end_clause)])
|
||||
if clause.expression != null:
|
||||
generate_expression(node, clause.expression)
|
||||
emit(Constants.ByteCode.JumpIfFalse, node, [Program.Operand.new(end_clause)])
|
||||
|
||||
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:
|
||||
emit(Constants.ByteCode.Label,node,[Program.Operand.new(end_clause)])
|
||||
if clause.expression != null:
|
||||
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.Label,node,[Program.Operand.new(endif)])
|
||||
emit(Constants.ByteCode.Label, node, [Program.Operand.new(endif)])
|
||||
|
||||
|
||||
#compile instructions for options
|
||||
func generate_option(node,option):
|
||||
# print('generating option')
|
||||
var destination : String = option.destination
|
||||
func generate_option(node, option):
|
||||
var destination = option.destination
|
||||
|
||||
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 :
|
||||
var line_iD : String = ''#tags not supported TODO: ADD TAG SUPPORT
|
||||
var string_iD = register_string(option.label,node.name,line_iD,option.line_number,[])
|
||||
var line_id = '' #TODO: ADD TAG SUPPORT
|
||||
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)])
|
||||
|
||||
|
||||
#compile instructions for assigning values
|
||||
func generate_assignment(node,assignment):
|
||||
# print('generating assign')
|
||||
#assignment
|
||||
func generate_assignment(node, assignment):
|
||||
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 :
|
||||
#this is combined op
|
||||
#get value of var
|
||||
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
|
||||
emit(Constants.ByteCode.PushVariable, node, [assignment.destination])
|
||||
generate_expression(node, assignment.value)
|
||||
|
||||
match assignment.operation:
|
||||
Constants.TokenType.AddAssign:
|
||||
emit(Constants.ByteCode.CallFunc,node,
|
||||
[Program.Operand.new(Constants.token_type_name(Constants.TokenType.Add))])
|
||||
emit(
|
||||
Constants.ByteCode.CallFunc,
|
||||
node,
|
||||
[Program.Operand.new(Constants.token_type_name(Constants.TokenType.Add))]
|
||||
)
|
||||
Constants.TokenType.MinusAssign:
|
||||
emit(Constants.ByteCode.CallFunc,node,
|
||||
[Program.Operand.new(Constants.token_type_name(Constants.TokenType.Minus))])
|
||||
emit(
|
||||
Constants.ByteCode.CallFunc,
|
||||
node,
|
||||
[Program.Operand.new(Constants.token_type_name(Constants.TokenType.Minus))]
|
||||
)
|
||||
Constants.TokenType.MultiplyAssign:
|
||||
emit(Constants.ByteCode.CallFunc,node,
|
||||
[Program.Operand.new(Constants.token_type_name(Constants.TokenType.MultiplyAssign))])
|
||||
emit(
|
||||
Constants.ByteCode.CallFunc,
|
||||
node,
|
||||
[Program.Operand.new(Constants.token_type_name(Constants.TokenType.MultiplyAssign))]
|
||||
)
|
||||
Constants.TokenType.DivideAssign:
|
||||
emit(Constants.ByteCode.CallFunc,node,
|
||||
[Program.Operand.new(Constants.token_type_name(Constants.TokenType.DivideAssign))])
|
||||
emit(
|
||||
Constants.ByteCode.CallFunc,
|
||||
node,
|
||||
[Program.Operand.new(Constants.token_type_name(Constants.TokenType.DivideAssign))]
|
||||
)
|
||||
_:
|
||||
printerr('Unable to generate assignment')
|
||||
|
||||
#stack contains destination value
|
||||
#store the top of the stack in variable
|
||||
emit(Constants.ByteCode.StoreVariable,node,[Program.Operand.new(assignment.destination)])
|
||||
emit(Constants.ByteCode.StoreVariable, node, [Program.Operand.new(assignment.destination)])
|
||||
emit(Constants.ByteCode.Pop, node)
|
||||
|
||||
#clean stack
|
||||
emit(Constants.ByteCode.Pop,node)
|
||||
|
||||
|
||||
#compile expression instructions
|
||||
func generate_expression(node,expression):
|
||||
# print('generating expression')
|
||||
#expression = value or func call
|
||||
func generate_expression(node, expression):
|
||||
match expression.type:
|
||||
Constants.ExpressionType.Value:
|
||||
generate_value(node,expression.value)
|
||||
generate_value(node, expression.value)
|
||||
Constants.ExpressionType.FunctionCall:
|
||||
#eval all parameters
|
||||
for param in expression.params:
|
||||
generate_expression(node,param)
|
||||
for parameter in expression.parameters:
|
||||
generate_expression(node, parameter)
|
||||
|
||||
#put the num of of params to stack
|
||||
emit(Constants.ByteCode.PushNumber,node,[Program.Operand.new(expression.params.size())])
|
||||
|
||||
#call function
|
||||
emit(Constants.ByteCode.CallFunc,node,[Program.Operand.new(expression.function)])
|
||||
emit(Constants.ByteCode.PushNumber, node, [Program.Operand.new(expression.parameters.size())])
|
||||
emit(Constants.ByteCode.CallFunc, node, [Program.Operand.new(expression.function)])
|
||||
_:
|
||||
printerr('no expression')
|
||||
printerr('No expression.')
|
||||
|
||||
#compile value instructions
|
||||
func generate_value(node,value):
|
||||
# print('generating value')
|
||||
#push value to stack
|
||||
func generate_value(node, value):
|
||||
match value.value.type:
|
||||
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:
|
||||
var id = register_string(value.value.as_string(),
|
||||
node.name,'',value.line_number,[])
|
||||
emit(Constants.ByteCode.PushString,node,[Program.Operand.new(id)])
|
||||
var id = register_string(
|
||||
value.value.as_string(),
|
||||
node.name,
|
||||
'',
|
||||
value.line_number,
|
||||
[]
|
||||
)
|
||||
emit(
|
||||
Constants.ByteCode.PushString,
|
||||
node,
|
||||
[Program.Operand.new(id)]
|
||||
)
|
||||
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:
|
||||
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:
|
||||
emit(Constants.ByteCode.PushNull,node)
|
||||
emit(Constants.ByteCode.PushNull, node)
|
||||
_:
|
||||
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(''))
|
||||
|
|
|
@ -3,30 +3,29 @@ class_name Lexer
|
|||
|
||||
const Constants = preload('res://addons/Wol/core/constants.gd')
|
||||
|
||||
const LINE_COMENT : String = '//'
|
||||
const FORWARD_SLASH : String = '/'
|
||||
const LINE_COMENT = '//'
|
||||
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'
|
||||
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 = '\\s*'
|
||||
|
||||
var WHITESPACE : String = '\\s*'
|
||||
var _states = {}
|
||||
var _defaultState
|
||||
var _currentState
|
||||
|
||||
var _states : Dictionary = {}
|
||||
var _defaultState : LexerState
|
||||
var _currentState : LexerState
|
||||
|
||||
var _indentStack : Array = []
|
||||
var _indentStack = []
|
||||
var _shouldTrackIndent : bool = false
|
||||
|
||||
var filename = ''
|
||||
|
@ -185,11 +184,11 @@ func tokenize():
|
|||
_indentStack.push_front(IntBoolPair.new(0, false))
|
||||
_shouldTrackIndent = false
|
||||
|
||||
var tokens : Array = []
|
||||
var tokens = []
|
||||
|
||||
_currentState = _defaultState
|
||||
|
||||
var lines : PoolStringArray = text.split(LINE_SEPARATOR)
|
||||
var lines = text.split(LINE_SEPARATOR)
|
||||
lines.append('')
|
||||
|
||||
var line_number : int = 1
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
extends Node
|
||||
|
||||
const Constants = preload('res://addons/Wol/core/constants.gd')
|
||||
var Value = load('res://addons/Wol/core/value.gd')
|
||||
|
||||
const EXECUTION_COMPLETE : String = 'execution_complete_command'
|
||||
|
||||
var NULL_VALUE = Value.new(null)
|
||||
const Value = preload('res://addons/Wol/core/value.gd')
|
||||
|
||||
# Function references to handlers
|
||||
var line_handler
|
||||
|
@ -18,9 +14,9 @@ var dialogue_finished_handler
|
|||
var dialogue
|
||||
var libraries
|
||||
var program
|
||||
var _state
|
||||
var state
|
||||
|
||||
var _currentNode
|
||||
var current_node
|
||||
|
||||
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(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(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
|
||||
#return true if successeful false if no node
|
||||
#of that name found
|
||||
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)
|
||||
return false
|
||||
|
||||
if !program.nodes.has(name):
|
||||
if not program.nodes.has(name):
|
||||
execution_state = Constants.ExecutionState.Stopped
|
||||
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
|
||||
|
||||
_currentNode = program.nodes[name]
|
||||
current_node = program.nodes[name]
|
||||
reset()
|
||||
_state.currentNodeName = name
|
||||
state.current_node_name = name
|
||||
node_start_handler.call_func(name)
|
||||
return true
|
||||
|
||||
func current_node_name()->String:
|
||||
return _currentNode.nodeName
|
||||
|
||||
func current_node():
|
||||
return _currentNode
|
||||
|
||||
func pause():
|
||||
execution_state = Constants.ExecutionState.Suspended
|
||||
|
||||
#stop exectuion
|
||||
func stop():
|
||||
execution_state = Constants.ExecutionState.Stopped
|
||||
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):
|
||||
if execution_state != Constants.ExecutionState.WaitingForOption:
|
||||
printerr('Unable to select option when dialogue not waiting for option')
|
||||
return false
|
||||
|
||||
if id < 0 or id >= _state.currentOptions.size():
|
||||
printerr('%d is not a valid option ' % id)
|
||||
if id < 0 or id >= state.current_options.size():
|
||||
printerr('%d is not a valid option!' % id)
|
||||
return false
|
||||
|
||||
var destination = _state.currentOptions[id].value
|
||||
_state.push_value(destination)
|
||||
_state.currentOptions.clear()
|
||||
var destination = state.current_options[id][1]
|
||||
state.push_value(destination)
|
||||
state.current_options.clear()
|
||||
|
||||
#no longer waiting for option
|
||||
execution_state = Constants.ExecutionState.Suspended
|
||||
|
||||
return true
|
||||
|
||||
func reset():
|
||||
_state = VmState.new()
|
||||
state = VmState.new()
|
||||
|
||||
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():
|
||||
if _currentNode == null:
|
||||
if current_node == null:
|
||||
printerr('Cannot run dialogue with no node selected')
|
||||
return false
|
||||
|
||||
if execution_state == Constants.ExecutionState.WaitingForOption:
|
||||
printerr('Cannot run while waiting for option')
|
||||
return false
|
||||
|
||||
|
||||
|
||||
execution_state = Constants.ExecutionState.Running
|
||||
|
||||
#execute instruction until something cool happens
|
||||
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)
|
||||
_state.programCounter += 1
|
||||
|
||||
if _state.programCounter >= _currentNode.instructions.size():
|
||||
node_finished_handler.call_func(_currentNode.nodeName)
|
||||
if state.programCounter >= current_node.instructions.size():
|
||||
node_finished_handler.call_func(current_node.nodeName)
|
||||
execution_state = Constants.ExecutionState.Stopped
|
||||
reset()
|
||||
dialogue_finished_handler.call_func()
|
||||
|
||||
return true
|
||||
|
||||
func find_label_instruction(label:String)->int:
|
||||
if !_currentNode.labels.has(label):
|
||||
printerr('Unknown label:'+label)
|
||||
func find_label_instruction(label):
|
||||
if not current_node.labels.has(label):
|
||||
printerr('Unknown label:' + label)
|
||||
return -1
|
||||
return _currentNode.labels[label]
|
||||
return current_node.labels[label]
|
||||
|
||||
func run_instruction(instruction)->bool:
|
||||
func run_instruction(instruction):
|
||||
match instruction.operation:
|
||||
Constants.ByteCode.Label:
|
||||
pass
|
||||
|
||||
# Jump to named label
|
||||
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:
|
||||
#look up string from string table
|
||||
#pass it to client as line
|
||||
# Lookup string and give back as line
|
||||
var key = instruction.operands[0].value
|
||||
|
||||
var line = program.strings[key]
|
||||
|
||||
#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
|
||||
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
|
||||
|
||||
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:
|
||||
pass#add format function
|
||||
|
||||
var command = Program.Command.new(commandText)
|
||||
|
||||
var pause = command_handler.call_func(command) as int
|
||||
if pause == Constants.HandlerState.PauseExecution:
|
||||
var return_state = command_handler.call_func(command)
|
||||
if return_state == Constants.HandlerState.PauseExecution:
|
||||
execution_state = Constants.ExecutionState.Suspended
|
||||
|
||||
Constants.ByteCode.PushString:
|
||||
#push String var to stack
|
||||
_state.push_value(instruction.operands[0].value)
|
||||
|
||||
Constants.ByteCode.PushNumber:
|
||||
#push number to stack
|
||||
_state.push_value(instruction.operands[0].value)
|
||||
|
||||
Constants.ByteCode.PushString, \
|
||||
Constants.ByteCode.PushNumber, \
|
||||
Constants.ByteCode.PushBool:
|
||||
#push boolean to stack
|
||||
_state.push_value(instruction.operands[0].value)
|
||||
|
||||
state.push_value(instruction.operands[0].value)
|
||||
Constants.ByteCode.PushNull:
|
||||
#push null t
|
||||
_state.push_value(NULL_VALUE)
|
||||
state.push_value(Value.new(null))
|
||||
|
||||
# Jump to named label if value of stack top is false
|
||||
Constants.ByteCode.JumpIfFalse:
|
||||
#jump to named label if value of stack top is false
|
||||
if !_state.peek_value().as_bool():
|
||||
_state.programCounter = find_label_instruction(instruction.operands[0].value)-1
|
||||
if not state.peek_value().as_bool():
|
||||
state.programCounter = find_label_instruction(instruction.operands[0].value) - 1
|
||||
|
||||
# Jump to label whose name is on the stack
|
||||
Constants.ByteCode.Jump:
|
||||
#jump to label whose name is on the stack
|
||||
var dest : String = _state.peek_value().as_string()
|
||||
_state.programCounter = find_label_instruction(dest)-1
|
||||
var destination = state.peek_value().as_string()
|
||||
state.programCounter = find_label_instruction(destination) - 1
|
||||
|
||||
Constants.ByteCode.Pop:
|
||||
#pop value from stack
|
||||
_state.pop_value()
|
||||
state.pop_value()
|
||||
|
||||
Constants.ByteCode.CallFunc:
|
||||
#call function with params on stack
|
||||
#push any return value to stack
|
||||
var functionName : String = instruction.operands[0].value
|
||||
var function_name = instruction.operands[0].value
|
||||
var function = libraries.get_function(function_name)
|
||||
var expected_parameter_count = function.parameter_count
|
||||
var actual_parameter_count = state.pop_value().as_number()
|
||||
|
||||
var function = libraries.get_function(functionName)
|
||||
|
||||
var expected_parameter_count : int = function.paramCount
|
||||
var actual_parameter_count : int = _state.pop_value().as_number()
|
||||
|
||||
#if function takes in -1 params disregard
|
||||
#expect the compiler to have placed the number of params
|
||||
#at the top of the stack
|
||||
if 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])
|
||||
if expected_parameter_count > 0 \
|
||||
and expected_parameter_count != actual_parameter_count:
|
||||
var error_data = [function_name, expected_parameter_count, actual_parameter_count]
|
||||
printerr('Function "%s" expected %d parameters but got %d instead' % error_data)
|
||||
return false
|
||||
|
||||
var result
|
||||
|
@ -236,85 +195,71 @@ func run_instruction(instruction)->bool:
|
|||
if actual_parameter_count == 0:
|
||||
result = function.invoke()
|
||||
else:
|
||||
var params : Array = []#value
|
||||
var params = []
|
||||
for _i in range(actual_parameter_count):
|
||||
params.push_front(_state.pop_value())
|
||||
params.push_front(state.pop_value())
|
||||
|
||||
result = function.invoke(params)
|
||||
|
||||
if function.returnsValue:
|
||||
_state.push_value(result)
|
||||
if function.returns_value:
|
||||
state.push_value(result)
|
||||
|
||||
Constants.ByteCode.PushVariable:
|
||||
#get content of variable and push to stack
|
||||
var name : String = instruction.operands[0].value
|
||||
# TODO: Reimplement variable storage
|
||||
var name = instruction.operands[0].value
|
||||
var loaded = dialogue.variable_storage.get_value(name)
|
||||
_state.push_value(loaded)
|
||||
state.push_value(loaded)
|
||||
|
||||
Constants.ByteCode.StoreVariable:
|
||||
#store top stack value to variable
|
||||
var top = _state.peek_value()
|
||||
var destination : String = instruction.operands[0].value
|
||||
dialogue.variable_storage.set_value(destination,top)
|
||||
var top = state.peek_value()
|
||||
var destination = instruction.operands[0].value
|
||||
dialogue.variable_storage.set_value(destination, top)
|
||||
|
||||
Constants.ByteCode.Stop:
|
||||
#stop execution and repost it
|
||||
node_finished_handler.call_func(_currentNode.name)
|
||||
node_finished_handler.call_func(current_node.name)
|
||||
dialogue_finished_handler.call_func()
|
||||
execution_state = Constants.ExecutionState.Stopped
|
||||
reset()
|
||||
|
||||
Constants.ByteCode.RunNode:
|
||||
#run a node
|
||||
var name : String
|
||||
|
||||
if (instruction.operands.size() == 0 || instruction.operands[0].value.empty()):
|
||||
#get string from stack and jump to node with that name
|
||||
name = _state.peek_value().value()
|
||||
else :
|
||||
var name = ''
|
||||
if instruction.operands.size() == 0 or instruction.operands[0].value.empty():
|
||||
name = state.peek_value().value()
|
||||
else:
|
||||
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)
|
||||
_state.programCounter-=1
|
||||
if pause == Constants.HandlerState.PauseExecution:
|
||||
state.programCounter -= 1
|
||||
if return_state == Constants.HandlerState.PauseExecution:
|
||||
execution_state = Constants.ExecutionState.Suspended
|
||||
|
||||
Constants.ByteCode.AddOption:
|
||||
# add an option to current state
|
||||
var key = instruction.operands[0].value
|
||||
|
||||
var line = program.strings[key]
|
||||
|
||||
# TODO: Add format functions supportk
|
||||
if instruction.operands.size() > 2:
|
||||
pass #formated text options
|
||||
pass
|
||||
|
||||
# line to show and node name
|
||||
_state.currentOptions.append(SimpleEntry.new(line, instruction.operands[1].value))
|
||||
state.current_options.append([line, instruction.operands[1].value])
|
||||
|
||||
Constants.ByteCode.ShowOptions:
|
||||
#show options - stop if none
|
||||
if _state.currentOptions.size() == 0:
|
||||
if state.current_options.size() == 0:
|
||||
execution_state = Constants.ExecutionState.Stopped
|
||||
reset()
|
||||
dialogue_finished_handler.call_func()
|
||||
return false
|
||||
|
||||
#present list of options
|
||||
var choices : Array = []#Option
|
||||
for optionIndex in range(_state.currentOptions.size()):
|
||||
var option : SimpleEntry = _state.currentOptions[optionIndex]
|
||||
choices.append(Program.Option.new(option.key, optionIndex, option.value))
|
||||
var choices = []
|
||||
for option_index in range(state.current_options.size()):
|
||||
var option = state.current_options[option_index]
|
||||
var line = option[0]
|
||||
var destination = option[1]
|
||||
choices.append(Program.Option.new(line, option_index, destination))
|
||||
|
||||
#we cant continue until option chosen
|
||||
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)
|
||||
|
||||
_:
|
||||
execution_state = Constants.ExecutionState.Stopped
|
||||
reset()
|
||||
|
@ -324,12 +269,10 @@ func run_instruction(instruction)->bool:
|
|||
return true
|
||||
|
||||
class VmState:
|
||||
var Value = load('res://addons/Wol/core/value.gd')
|
||||
|
||||
var currentNodeName : String
|
||||
var programCounter : int = 0
|
||||
var currentOptions : Array = []#SimpleEntry
|
||||
var stack : Array = [] #Value
|
||||
var current_node_name = ''
|
||||
var programCounter = 0
|
||||
var current_options = []
|
||||
var stack = []
|
||||
|
||||
func push_value(value)->void:
|
||||
if value is Value:
|
||||
|
@ -345,11 +288,3 @@ class VmState:
|
|||
|
||||
func clear_stack():
|
||||
stack.clear()
|
||||
|
||||
class SimpleEntry:
|
||||
var key
|
||||
var value
|
||||
|
||||
func _init(_key, _value):
|
||||
key = _key
|
||||
value = _value
|
||||
|
|
Reference in a new issue