first commit
This commit is contained in:
commit
7238986fa1
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
.import
|
||||||
|
.DS_Store
|
8
Dialogue.tscn
Normal file
8
Dialogue.tscn
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[gd_scene format=2]
|
||||||
|
|
||||||
|
[node name="Dialogue" type="Control"]
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
__meta__ = {
|
||||||
|
"_edit_use_anchors_": false
|
||||||
|
}
|
BIN
addons/Wol/.!11255!.DS_Store
Normal file
BIN
addons/Wol/.!11255!.DS_Store
Normal file
Binary file not shown.
0
addons/Wol/assets/.!11279!icon.png
Normal file
0
addons/Wol/assets/.!11279!icon.png
Normal file
0
addons/Wol/assets/.!11280!.DS_Store
Normal file
0
addons/Wol/assets/.!11280!.DS_Store
Normal file
BIN
addons/Wol/assets/icon.png
Normal file
BIN
addons/Wol/assets/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
35
addons/Wol/assets/icon.png.import
Normal file
35
addons/Wol/assets/icon.png.import
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="StreamTexture"
|
||||||
|
path="res://.import/icon.png-79ec48c27aef60cbae9d34f0545addad.stex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://addons/Wol/assets/icon.png"
|
||||||
|
dest_files=[ "res://.import/icon.png-79ec48c27aef60cbae9d34f0545addad.stex" ]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_mode=0
|
||||||
|
compress/bptc_ldr=0
|
||||||
|
compress/normal_map=0
|
||||||
|
flags/repeat=0
|
||||||
|
flags/filter=true
|
||||||
|
flags/mipmaps=false
|
||||||
|
flags/anisotropic=false
|
||||||
|
flags/srgb=2
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/HDR_as_SRGB=false
|
||||||
|
process/invert_color=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
stream=false
|
||||||
|
size_limit=0
|
||||||
|
detect_3d=true
|
||||||
|
svg/scale=1.0
|
234
addons/Wol/autoloads/execution_states.gd
Normal file
234
addons/Wol/autoloads/execution_states.gd
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
#VM Execution States
|
||||||
|
|
||||||
|
enum ExecutionState{
|
||||||
|
Stopped,
|
||||||
|
Running,
|
||||||
|
WaitingForOption,
|
||||||
|
Suspended
|
||||||
|
}
|
||||||
|
|
||||||
|
enum HandlerState{
|
||||||
|
PauseExecution,
|
||||||
|
ContinueExecution
|
||||||
|
}
|
||||||
|
|
||||||
|
#Compile Status return
|
||||||
|
enum CompileStatus {
|
||||||
|
Succeeded, SucceededUntaggedStrings,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ByteCode{
|
||||||
|
# opA = string: label name
|
||||||
|
Label,
|
||||||
|
# opA = string: label name
|
||||||
|
JumpTo,
|
||||||
|
# peek string from stack and jump to that label
|
||||||
|
Jump,
|
||||||
|
# opA = int: string number
|
||||||
|
RunLine,
|
||||||
|
# opA = string: command text
|
||||||
|
RunCommand,
|
||||||
|
# opA = int: string number for option to add
|
||||||
|
AddOption,
|
||||||
|
# present the current list of options, then clear the list; most recently
|
||||||
|
# selected option will be on the top of the stack
|
||||||
|
ShowOptions,
|
||||||
|
# opA = int: string number in table; push string to stack
|
||||||
|
PushString,
|
||||||
|
# opA = float: number to push to stack
|
||||||
|
PushNumber,
|
||||||
|
# opA = int (0 or 1): bool to push to stack
|
||||||
|
PushBool,
|
||||||
|
# pushes a null value onto the stack
|
||||||
|
PushNull,
|
||||||
|
# opA = string: label name if top of stack is not null, zero or false, jumps
|
||||||
|
# to that label
|
||||||
|
JumpIfFalse,
|
||||||
|
# discard top of stack
|
||||||
|
Pop,
|
||||||
|
# opA = string; looks up function, pops as many arguments as needed, result is
|
||||||
|
# pushed to stack
|
||||||
|
CallFunc,
|
||||||
|
# opA = name of variable to get value of and push to stack
|
||||||
|
PushVariable,
|
||||||
|
# opA = name of variable to store top of stack in
|
||||||
|
StoreVariable,
|
||||||
|
# stops execution
|
||||||
|
Stop,
|
||||||
|
# run the node whose name is at the top of the stack
|
||||||
|
RunNode
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TokenType {
|
||||||
|
|
||||||
|
#0 Special tokens
|
||||||
|
Whitespace, Indent, Dedent, EndOfLine, EndOfInput,
|
||||||
|
|
||||||
|
#5 Numbers. Everybody loves a number
|
||||||
|
Number,
|
||||||
|
|
||||||
|
#6 Strings. Everybody also loves a string
|
||||||
|
Str,
|
||||||
|
|
||||||
|
#7 '#'
|
||||||
|
TagMarker,
|
||||||
|
|
||||||
|
#8 Command syntax ("<<foo>>")
|
||||||
|
BeginCommand, EndCommand,
|
||||||
|
|
||||||
|
#10 Variables ("$foo")
|
||||||
|
Variable,
|
||||||
|
|
||||||
|
#11 Shortcut syntax ("->")
|
||||||
|
ShortcutOption,
|
||||||
|
|
||||||
|
#12 Option syntax ("[[Let's go here|Destination]]")
|
||||||
|
OptionStart, # [[
|
||||||
|
OptionDelimit, # |
|
||||||
|
OptionEnd, # ]]
|
||||||
|
|
||||||
|
#15 Command types (specially recognised command word)
|
||||||
|
IfToken, ElseIf, ElseToken, EndIf, Set,
|
||||||
|
|
||||||
|
#20 Boolean values
|
||||||
|
TrueToken, FalseToken,
|
||||||
|
|
||||||
|
#22 The null value
|
||||||
|
NullToken,
|
||||||
|
|
||||||
|
#23 Parentheses
|
||||||
|
LeftParen, RightParen,
|
||||||
|
|
||||||
|
#25 Parameter delimiters
|
||||||
|
Comma,
|
||||||
|
|
||||||
|
#26 Operators
|
||||||
|
EqualTo, # ==, eq, is
|
||||||
|
GreaterThan, # >, gt
|
||||||
|
GreaterThanOrEqualTo, # >=, gte
|
||||||
|
LessThan, # <, lt
|
||||||
|
LessThanOrEqualTo, # <=, lte
|
||||||
|
NotEqualTo, # !=, neq
|
||||||
|
|
||||||
|
#32 Logical operators
|
||||||
|
Or, # ||, or
|
||||||
|
And, # &&, and
|
||||||
|
Xor, # ^, xor
|
||||||
|
Not, # !, not
|
||||||
|
|
||||||
|
# this guy's special because '=' can mean either 'equal to'
|
||||||
|
#36 or 'becomes' depending on context
|
||||||
|
EqualToOrAssign, # =, to
|
||||||
|
|
||||||
|
#37
|
||||||
|
UnaryMinus, # -; this is differentiated from Minus
|
||||||
|
# when parsing expressions
|
||||||
|
|
||||||
|
#38
|
||||||
|
Add, # +
|
||||||
|
Minus, # -
|
||||||
|
Multiply, # *
|
||||||
|
Divide, # /
|
||||||
|
Modulo, # %
|
||||||
|
|
||||||
|
#43
|
||||||
|
AddAssign, # +=
|
||||||
|
MinusAssign, # -=
|
||||||
|
MultiplyAssign, # *=
|
||||||
|
DivideAssign, # /=
|
||||||
|
|
||||||
|
Comment, # a run of text that we ignore
|
||||||
|
|
||||||
|
Identifier, # a single word (used for functions)
|
||||||
|
|
||||||
|
Text # a run of text until we hit other syntax
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum ExpressionType{
|
||||||
|
Value, FunctionCall
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum StatementTypes{
|
||||||
|
CustomCommand,
|
||||||
|
ShortcutOptionGroup,
|
||||||
|
Block,
|
||||||
|
IfStatement,
|
||||||
|
OptionStatement,
|
||||||
|
AssignmentStatement,
|
||||||
|
Line
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ValueType{
|
||||||
|
Number,
|
||||||
|
Str,
|
||||||
|
Boolean,
|
||||||
|
Variable,
|
||||||
|
Nullean#null lel
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultValue(type):
|
||||||
|
pass
|
||||||
|
|
||||||
|
static func token_type_name(value:int)->String:
|
||||||
|
for key in TokenType.keys():
|
||||||
|
if TokenType[key] == value:
|
||||||
|
return key
|
||||||
|
return "NOTVALID"
|
||||||
|
|
||||||
|
static func merge_dir(target, patch):
|
||||||
|
for key in patch:
|
||||||
|
target[key] = patch[key]
|
||||||
|
|
||||||
|
|
||||||
|
#same as top one woops
|
||||||
|
func token_name(type)->String:
|
||||||
|
var string : String = ""
|
||||||
|
|
||||||
|
for key in TokenType.keys():
|
||||||
|
if TokenType[key] == type:
|
||||||
|
return key
|
||||||
|
return string
|
||||||
|
|
||||||
|
func bytecode_name(bytecode):
|
||||||
|
return [
|
||||||
|
"Label",
|
||||||
|
"JumpTo",
|
||||||
|
"Jump",
|
||||||
|
"RunLine",
|
||||||
|
"RunCommand",
|
||||||
|
"AddOption",
|
||||||
|
"ShowOptions",
|
||||||
|
"PushString",
|
||||||
|
"PushNumber",
|
||||||
|
"PushBool",
|
||||||
|
"PushNull",
|
||||||
|
"JumpIfFalse",
|
||||||
|
"Pop",
|
||||||
|
"CallFunc",
|
||||||
|
"PushVariable",
|
||||||
|
"StoreVariable",
|
||||||
|
"Stop",
|
||||||
|
"RunNode"
|
||||||
|
][bytecode]
|
||||||
|
|
||||||
|
#combine all the programs in the provided array
|
||||||
|
static func combine_programs(programs : Array = []):
|
||||||
|
var YarnProgram = load("res://addons/Wol/core/program/program.gd")
|
||||||
|
if programs.size() == 0:
|
||||||
|
printerr("no programs to combine - you failure")
|
||||||
|
return
|
||||||
|
var p = YarnProgram.new()
|
||||||
|
|
||||||
|
for program in programs:
|
||||||
|
for nodeKey in program.yarnNodes.keys():
|
||||||
|
if p.yarnNodes.has(nodeKey):
|
||||||
|
printerr("Program with duplicate node names %s "% nodeKey)
|
||||||
|
return
|
||||||
|
p.yarnNodes[nodeKey] = program.yarnNodes[nodeKey]
|
||||||
|
|
||||||
|
return p
|
||||||
|
|
20
addons/Wol/core/compiler/analyzer.gd
Normal file
20
addons/Wol/core/compiler/analyzer.gd
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
|
||||||
|
# Declare member variables here. Examples:
|
||||||
|
# var a = 2
|
||||||
|
# var b = "text"
|
||||||
|
|
||||||
|
|
||||||
|
# Called when the node enters the scene tree for the first time.
|
||||||
|
func _ready():
|
||||||
|
pass # Replace with function body.
|
||||||
|
|
||||||
|
|
||||||
|
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||||
|
#func _process(delta):
|
||||||
|
# pass
|
||||||
|
|
||||||
|
|
||||||
|
#This is a test of the keyboard omg is it working i hope so please make sure it works 12345678910
|
||||||
|
#Do you think that it works. So maybe PPPP
|
463
addons/Wol/core/compiler/compiler.gd
Normal file
463
addons/Wol/core/compiler/compiler.gd
Normal file
|
@ -0,0 +1,463 @@
|
||||||
|
extends Object
|
||||||
|
|
||||||
|
const Lexer = preload("res://addons/Wol/core/compiler/lexer.gd")
|
||||||
|
const LineInfo = preload("res://addons/Wol/core/program/yarn_line.gd")
|
||||||
|
const YarnNode = preload("res://addons/Wol/core/program/yarn_node.gd")
|
||||||
|
const Instruction = preload("res://addons/Wol/core/program/instruction.gd")
|
||||||
|
const YarnProgram = preload("res://addons/Wol/core/program/program.gd")
|
||||||
|
const Operand = preload("res://addons/Wol/core/program/operand.gd")
|
||||||
|
|
||||||
|
|
||||||
|
#patterns
|
||||||
|
const INVALIDTITLENAME = "[\\[<>\\]{}\\|:\\s#\\$]"
|
||||||
|
|
||||||
|
#ERROR Codes
|
||||||
|
const NO_ERROR = 0x00
|
||||||
|
const LEXER_FAILURE = 0x01
|
||||||
|
const PARSER_FAILURE = 0x02
|
||||||
|
const INVALID_HEADER = 0x04
|
||||||
|
const DUPLICATE_NODES_IN_PROGRAM = 0x08
|
||||||
|
const ERR_COMPILATION_FAILED = 0x10
|
||||||
|
|
||||||
|
var _errors : int
|
||||||
|
var _lastError : int
|
||||||
|
|
||||||
|
#-----Class vars
|
||||||
|
var _currentNode : YarnNode
|
||||||
|
var _rawText : bool
|
||||||
|
var _fileName : String
|
||||||
|
var _containsImplicitStringTags : bool
|
||||||
|
var _labelCount : int = 0
|
||||||
|
|
||||||
|
#<String, LineInfo>
|
||||||
|
var _stringTable : Dictionary = {}
|
||||||
|
var _stringCount : int = 0
|
||||||
|
#<int, YarnGlobals.TokenType>
|
||||||
|
var _tokens : Dictionary = {}
|
||||||
|
|
||||||
|
static func compile_string(source: String, filename: String) -> YarnProgram:
|
||||||
|
var Parser = load("res://addons/Wol/core/compiler/parser.gd")
|
||||||
|
var Compiler = load("res://addons/Wol/core/compiler/compiler.gd")
|
||||||
|
|
||||||
|
var compiler = Compiler.new()
|
||||||
|
compiler._fileName = filename
|
||||||
|
|
||||||
|
#--------------Nodes
|
||||||
|
var headerSep : RegEx = RegEx.new()
|
||||||
|
headerSep.compile("---(\r\n|\r|\n)")
|
||||||
|
var headerProperty : RegEx = RegEx.new()
|
||||||
|
headerProperty.compile("(?<field>.*): *(?<value>.*)")
|
||||||
|
|
||||||
|
assert(not not headerSep.search(source), "No headers found")
|
||||||
|
|
||||||
|
var lineNumber: int = 0
|
||||||
|
|
||||||
|
var sourceLines : Array = source.split('\n',false)
|
||||||
|
for i in range(sourceLines.size()):
|
||||||
|
sourceLines[i] = sourceLines[i].strip_edges(false,true)
|
||||||
|
|
||||||
|
var parsedNodes : Array = []
|
||||||
|
|
||||||
|
while lineNumber < sourceLines.size():
|
||||||
|
|
||||||
|
var title : String
|
||||||
|
var body : String
|
||||||
|
|
||||||
|
#get title
|
||||||
|
while true:
|
||||||
|
var line : String = sourceLines[lineNumber]
|
||||||
|
lineNumber+=1
|
||||||
|
|
||||||
|
if !line.empty():
|
||||||
|
var result = headerProperty.search(line)
|
||||||
|
if result != null :
|
||||||
|
var field : String = result.get_string("field")
|
||||||
|
var value : String = result.get_string("value")
|
||||||
|
|
||||||
|
if field == "title":
|
||||||
|
title = value
|
||||||
|
|
||||||
|
if(lineNumber >= sourceLines.size() || sourceLines[lineNumber] == "---"):
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
lineNumber+=1
|
||||||
|
|
||||||
|
#past header
|
||||||
|
var bodyLines : PoolStringArray = []
|
||||||
|
|
||||||
|
while lineNumber < sourceLines.size() && sourceLines[lineNumber]!="===":
|
||||||
|
bodyLines.append(sourceLines[lineNumber])
|
||||||
|
lineNumber+=1
|
||||||
|
|
||||||
|
lineNumber+=1
|
||||||
|
|
||||||
|
body = bodyLines.join('\n')
|
||||||
|
var lexer = Lexer.new()
|
||||||
|
|
||||||
|
var tokens : Array = lexer.tokenize(body)
|
||||||
|
var parser = Parser.new(tokens)
|
||||||
|
|
||||||
|
var parserNode = parser.parse_node()
|
||||||
|
|
||||||
|
parserNode.name = title
|
||||||
|
parsedNodes.append(parserNode)
|
||||||
|
while lineNumber < sourceLines.size() && sourceLines[lineNumber].empty():
|
||||||
|
lineNumber+=1
|
||||||
|
|
||||||
|
#--- End parsing nodes---
|
||||||
|
|
||||||
|
var program = YarnProgram.new()
|
||||||
|
|
||||||
|
#compile nodes
|
||||||
|
for node in parsedNodes:
|
||||||
|
compiler.compile_node(program, node)
|
||||||
|
|
||||||
|
for key in compiler._stringTable:
|
||||||
|
program.yarnStrings[key] = compiler._stringTable[key]
|
||||||
|
|
||||||
|
return program
|
||||||
|
|
||||||
|
func compile_node(program:YarnProgram,parsedNode)->void:
|
||||||
|
if program.yarnNodes.has(parsedNode.name):
|
||||||
|
emit_error(DUPLICATE_NODES_IN_PROGRAM)
|
||||||
|
printerr("Duplicate node in program: %s" % parsedNode.name)
|
||||||
|
else:
|
||||||
|
var nodeCompiled : YarnNode = YarnNode.new()
|
||||||
|
|
||||||
|
nodeCompiled.nodeName = parsedNode.name
|
||||||
|
nodeCompiled.tags = parsedNode.tags
|
||||||
|
|
||||||
|
#raw text
|
||||||
|
if parsedNode.source != null && !parsedNode.source.empty():
|
||||||
|
nodeCompiled.sourceId = register_string(parsedNode.source,parsedNode.name,
|
||||||
|
"line:"+parsedNode.name, 0, [])
|
||||||
|
else:
|
||||||
|
#compile node
|
||||||
|
var startLabel : String = register_label()
|
||||||
|
emit(YarnGlobals.ByteCode.Label,nodeCompiled,[Operand.new(startLabel)])
|
||||||
|
|
||||||
|
for statement in parsedNode.statements:
|
||||||
|
generate_statement(nodeCompiled,statement)
|
||||||
|
|
||||||
|
|
||||||
|
#add options
|
||||||
|
#todo: add parser flag
|
||||||
|
|
||||||
|
var danglingOptions = false
|
||||||
|
for instruction in nodeCompiled.instructions :
|
||||||
|
if instruction.operation == YarnGlobals.ByteCode.AddOption:
|
||||||
|
danglingOptions = true
|
||||||
|
if instruction.operation == YarnGlobals.ByteCode.ShowOptions:
|
||||||
|
danglingOptions = false
|
||||||
|
|
||||||
|
if danglingOptions:
|
||||||
|
emit(YarnGlobals.ByteCode.ShowOptions, nodeCompiled)
|
||||||
|
emit(YarnGlobals.ByteCode.RunNode, nodeCompiled)
|
||||||
|
else:
|
||||||
|
emit(YarnGlobals.ByteCode.Stop, nodeCompiled)
|
||||||
|
|
||||||
|
|
||||||
|
program.yarnNodes[nodeCompiled.nodeName] = nodeCompiled
|
||||||
|
|
||||||
|
func register_string(text:String,nodeName:String,id:String="",lineNumber:int=-1,tags:Array=[])->String:
|
||||||
|
var lineIdUsed : String
|
||||||
|
|
||||||
|
var implicit : bool
|
||||||
|
|
||||||
|
if id.empty():
|
||||||
|
lineIdUsed = "%s-%s-%d" % [self._fileName,nodeName,self._stringCount]
|
||||||
|
self._stringCount+=1
|
||||||
|
|
||||||
|
#use this when we generate implicit tags
|
||||||
|
#they are not saved and are generated
|
||||||
|
#aka dummy tags that change on each compilation
|
||||||
|
_containsImplicitStringTags = true
|
||||||
|
|
||||||
|
implicit = true
|
||||||
|
else :
|
||||||
|
lineIdUsed = id
|
||||||
|
implicit = false
|
||||||
|
|
||||||
|
var stringInfo : LineInfo = LineInfo.new(text,nodeName,lineNumber,_fileName,implicit,tags)
|
||||||
|
#add to string table and return id
|
||||||
|
self._stringTable[lineIdUsed] = stringInfo
|
||||||
|
|
||||||
|
return lineIdUsed
|
||||||
|
|
||||||
|
func register_label(comment:String="")->String:
|
||||||
|
_labelCount+=1
|
||||||
|
return "L%s%s" %[ _labelCount , comment]
|
||||||
|
|
||||||
|
func emit(bytecode,node:YarnNode=_currentNode,operands:Array=[]):
|
||||||
|
var instruction : Instruction = Instruction.new(null)
|
||||||
|
instruction.operation = bytecode
|
||||||
|
instruction.operands = operands
|
||||||
|
# print("emitting instruction to %s"%node.nodeName)
|
||||||
|
|
||||||
|
if(node == null):
|
||||||
|
printerr("trying to emit to null node with byteCode: %s" % bytecode)
|
||||||
|
return;
|
||||||
|
node.instructions.append(instruction)
|
||||||
|
if bytecode == YarnGlobals.ByteCode.Label :
|
||||||
|
#add to label table
|
||||||
|
node.labels[instruction.operands[0].value] = node.instructions.size()-1
|
||||||
|
|
||||||
|
|
||||||
|
func get_string_tokens()->Array:
|
||||||
|
return []
|
||||||
|
|
||||||
|
#compile header
|
||||||
|
func generate_header():
|
||||||
|
pass
|
||||||
|
|
||||||
|
#compile instructions for statements
|
||||||
|
#this will walk through all child branches
|
||||||
|
#of the parse tree
|
||||||
|
func generate_statement(node,statement):
|
||||||
|
# print("generating statement")
|
||||||
|
match statement.type:
|
||||||
|
YarnGlobals.StatementTypes.CustomCommand:
|
||||||
|
generate_custom_command(node,statement.customCommand)
|
||||||
|
YarnGlobals.StatementTypes.ShortcutOptionGroup:
|
||||||
|
generate_shortcut_group(node,statement.shortcutOptionGroup)
|
||||||
|
YarnGlobals.StatementTypes.Block:
|
||||||
|
generate_block(node,statement.block.statements)
|
||||||
|
YarnGlobals.StatementTypes.IfStatement:
|
||||||
|
generate_if(node,statement.ifStatement)
|
||||||
|
YarnGlobals.StatementTypes.OptionStatement:
|
||||||
|
generate_option(node,statement.optionStatement)
|
||||||
|
YarnGlobals.StatementTypes.AssignmentStatement:
|
||||||
|
generate_assignment(node,statement.assignment)
|
||||||
|
YarnGlobals.StatementTypes.Line:
|
||||||
|
generate_line(node,statement,statement.line)
|
||||||
|
_:
|
||||||
|
emit_error(ERR_COMPILATION_FAILED)
|
||||||
|
printerr("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
|
||||||
|
if command.expression != null:
|
||||||
|
generate_expression(node,command.expression)
|
||||||
|
else:
|
||||||
|
var commandString = command.clientCommand
|
||||||
|
if commandString == "stop":
|
||||||
|
emit(YarnGlobals.ByteCode.Stop,node)
|
||||||
|
else :
|
||||||
|
emit(YarnGlobals.ByteCode.RunCommand,node,[Operand.new(commandString)])
|
||||||
|
|
||||||
|
#compile instructions for linetags and use them
|
||||||
|
# \#line:number
|
||||||
|
func generate_line(node,statement,line:String):
|
||||||
|
var num : String = register_string(line,node.nodeName,"",statement.lineNumber,[]);
|
||||||
|
emit(YarnGlobals.ByteCode.RunLine,node,[Operand.new(num)])
|
||||||
|
|
||||||
|
func generate_shortcut_group(node,shortcutGroup):
|
||||||
|
# print("generating shortcutoptopn group")
|
||||||
|
var end : String = register_label("group_end")
|
||||||
|
|
||||||
|
var labels : Array = []#String
|
||||||
|
|
||||||
|
var optionCount : int = 0
|
||||||
|
|
||||||
|
for option in shortcutGroup.options:
|
||||||
|
var opDestination : String = register_label("option_%s"%[optionCount+1])
|
||||||
|
labels.append(opDestination)
|
||||||
|
|
||||||
|
var endofClause : String = ""
|
||||||
|
|
||||||
|
if option.condition != null :
|
||||||
|
endofClause = register_label("conditional_%s"%optionCount)
|
||||||
|
generate_expression(node,option.condition)
|
||||||
|
emit(YarnGlobals.ByteCode.JumpIfFalse,node,[Operand.new(endofClause)])
|
||||||
|
|
||||||
|
var labelLineId : String = ""#no tag TODO: ADD TAG SUPPORT
|
||||||
|
var labelStringId : String = register_string(option.label,node.nodeName,
|
||||||
|
labelLineId,option.lineNumber,[])
|
||||||
|
|
||||||
|
emit(YarnGlobals.ByteCode.AddOption,node,[Operand.new(labelStringId),Operand.new(opDestination)])
|
||||||
|
|
||||||
|
if option.condition != null :
|
||||||
|
emit(YarnGlobals.ByteCode.Label,node,[Operand.new(endofClause)])
|
||||||
|
emit(YarnGlobals.ByteCode.Pop,node)
|
||||||
|
|
||||||
|
optionCount+=1
|
||||||
|
|
||||||
|
emit(YarnGlobals.ByteCode.ShowOptions,node)
|
||||||
|
emit(YarnGlobals.ByteCode.Jump,node)
|
||||||
|
|
||||||
|
optionCount = 0
|
||||||
|
|
||||||
|
for option in shortcutGroup.options:
|
||||||
|
emit(YarnGlobals.ByteCode.Label,node,[Operand.new(labels[optionCount])])
|
||||||
|
|
||||||
|
if option.node != null :
|
||||||
|
generate_block(node,option.node.statements)
|
||||||
|
emit(YarnGlobals.ByteCode.JumpTo,node,[Operand.new(end)])
|
||||||
|
optionCount+=1
|
||||||
|
|
||||||
|
#end of option group
|
||||||
|
emit(YarnGlobals.ByteCode.Label,node,[Operand.new(end)])
|
||||||
|
#clean up
|
||||||
|
emit(YarnGlobals.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():
|
||||||
|
for statement in statements:
|
||||||
|
generate_statement(node,statement)
|
||||||
|
|
||||||
|
|
||||||
|
#compile if branching instructions
|
||||||
|
func generate_if(node,ifStatement):
|
||||||
|
# print("generating if")
|
||||||
|
#jump to label @ end of every clause
|
||||||
|
var endif : String = register_label("endif")
|
||||||
|
|
||||||
|
for clause in ifStatement.clauses:
|
||||||
|
var endClause : String = register_label("skip_clause")
|
||||||
|
|
||||||
|
if clause.expression!=null:
|
||||||
|
generate_expression(node,clause.expression)
|
||||||
|
emit(YarnGlobals.ByteCode.JumpIfFalse,node,[Operand.new(endClause)])
|
||||||
|
|
||||||
|
generate_block(node,clause.statements)
|
||||||
|
emit(YarnGlobals.ByteCode.JumpTo,node,[Operand.new(endif)])
|
||||||
|
|
||||||
|
if clause.expression!=null:
|
||||||
|
emit(YarnGlobals.ByteCode.Label,node,[Operand.new(endClause)])
|
||||||
|
|
||||||
|
if clause.expression!=null:
|
||||||
|
emit(YarnGlobals.ByteCode.Pop)
|
||||||
|
|
||||||
|
|
||||||
|
emit(YarnGlobals.ByteCode.Label,node,[Operand.new(endif)])
|
||||||
|
|
||||||
|
|
||||||
|
#compile instructions for options
|
||||||
|
func generate_option(node,option):
|
||||||
|
# print("generating option")
|
||||||
|
var destination : String = option.destination
|
||||||
|
|
||||||
|
if option.label == null || option.label.empty():
|
||||||
|
#jump to another node
|
||||||
|
emit(YarnGlobals.ByteCode.RunNode,node,[Operand.new(destination)])
|
||||||
|
else :
|
||||||
|
var lineID : String = ""#tags not supported TODO: ADD TAG SUPPORT
|
||||||
|
var stringID = register_string(option.label,node.nodeName,lineID,option.lineNumber,[])
|
||||||
|
|
||||||
|
emit(YarnGlobals.ByteCode.AddOption,node,[Operand.new(stringID),Operand.new(destination)])
|
||||||
|
|
||||||
|
|
||||||
|
#compile instructions for assigning values
|
||||||
|
func generate_assignment(node,assignment):
|
||||||
|
# print("generating assign")
|
||||||
|
#assignment
|
||||||
|
if assignment.operation == YarnGlobals.TokenType.EqualToOrAssign:
|
||||||
|
#evaluate the expression to a value for the stack
|
||||||
|
generate_expression(node,assignment.value)
|
||||||
|
else :
|
||||||
|
#this is combined op
|
||||||
|
#get value of var
|
||||||
|
emit(YarnGlobals.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:
|
||||||
|
YarnGlobals.TokenType.AddAssign:
|
||||||
|
emit(YarnGlobals.ByteCode.CallFunc,node,
|
||||||
|
[Operand.new(YarnGlobals.token_type_name(YarnGlobals.TokenType.Add))])
|
||||||
|
YarnGlobals.TokenType.MinusAssign:
|
||||||
|
emit(YarnGlobals.ByteCode.CallFunc,node,
|
||||||
|
[Operand.new(YarnGlobals.token_type_name(YarnGlobals.TokenType.Minus))])
|
||||||
|
YarnGlobals.TokenType.MultiplyAssign:
|
||||||
|
emit(YarnGlobals.ByteCode.CallFunc,node,
|
||||||
|
[Operand.new(YarnGlobals.token_type_name(YarnGlobals.TokenType.MultiplyAssign))])
|
||||||
|
YarnGlobals.TokenType.DivideAssign:
|
||||||
|
emit(YarnGlobals.ByteCode.CallFunc,node,
|
||||||
|
[Operand.new(YarnGlobals.token_type_name(YarnGlobals.TokenType.DivideAssign))])
|
||||||
|
_:
|
||||||
|
printerr("Unable to generate assignment")
|
||||||
|
|
||||||
|
#stack contains destination value
|
||||||
|
#store the top of the stack in variable
|
||||||
|
emit(YarnGlobals.ByteCode.StoreVariable,node,[Operand.new(assignment.destination)])
|
||||||
|
|
||||||
|
#clean stack
|
||||||
|
emit(YarnGlobals.ByteCode.Pop,node)
|
||||||
|
|
||||||
|
|
||||||
|
#compile expression instructions
|
||||||
|
func generate_expression(node,expression):
|
||||||
|
# print("generating expression")
|
||||||
|
#expression = value || func call
|
||||||
|
match expression.type:
|
||||||
|
YarnGlobals.ExpressionType.Value:
|
||||||
|
generate_value(node,expression.value)
|
||||||
|
YarnGlobals.ExpressionType.FunctionCall:
|
||||||
|
#eval all parameters
|
||||||
|
for param in expression.params:
|
||||||
|
generate_expression(node,param)
|
||||||
|
|
||||||
|
#put the num of of params to stack
|
||||||
|
emit(YarnGlobals.ByteCode.PushNumber,node,[Operand.new(expression.params.size())])
|
||||||
|
|
||||||
|
#call function
|
||||||
|
emit(YarnGlobals.ByteCode.CallFunc,node,[Operand.new(expression.function)])
|
||||||
|
_:
|
||||||
|
printerr("no expression")
|
||||||
|
|
||||||
|
#compile value instructions
|
||||||
|
func generate_value(node,value):
|
||||||
|
# print("generating value")
|
||||||
|
#push value to stack
|
||||||
|
match value.value.type:
|
||||||
|
YarnGlobals.ValueType.Number:
|
||||||
|
emit(YarnGlobals.ByteCode.PushNumber,node,[Operand.new(value.value.as_number())])
|
||||||
|
YarnGlobals.ValueType.Str:
|
||||||
|
var id : String = register_string(value.value.as_string(),
|
||||||
|
node.nodeName,"",value.lineNumber,[])
|
||||||
|
emit(YarnGlobals.ByteCode.PushString,node,[Operand.new(id)])
|
||||||
|
YarnGlobals.ValueType.Boolean:
|
||||||
|
emit(YarnGlobals.ByteCode.PushBool,node,[Operand.new(value.value.as_bool())])
|
||||||
|
YarnGlobals.ValueType.Variable:
|
||||||
|
emit(YarnGlobals.ByteCode.PushVariable,node,[Operand.new(value.value.variable)])
|
||||||
|
YarnGlobals.ValueType.Nullean:
|
||||||
|
emit(YarnGlobals.ByteCode.PushNull,node)
|
||||||
|
_:
|
||||||
|
printerr("Unrecognized valuenode type: %s" % value.value.type)
|
||||||
|
|
||||||
|
|
||||||
|
#get the error flags
|
||||||
|
func get_errors()->int:
|
||||||
|
return _errors
|
||||||
|
|
||||||
|
#get the last error code reported
|
||||||
|
func get_last_error()->int:
|
||||||
|
return _lastError
|
||||||
|
|
||||||
|
func clear_errors()->void:
|
||||||
|
_errors = NO_ERROR
|
||||||
|
_lastError = NO_ERROR
|
||||||
|
|
||||||
|
func emit_error(error : int)->void:
|
||||||
|
_lastError = error
|
||||||
|
_errors |= _lastError
|
||||||
|
|
||||||
|
|
||||||
|
static func print_tokens(tokens:Array=[]):
|
||||||
|
var list : PoolStringArray = []
|
||||||
|
list.append("\n")
|
||||||
|
for token in tokens:
|
||||||
|
list.append("%s (%s line %s)\n"%[YarnGlobals.token_type_name(token.type),token.value,token.lineNumber])
|
||||||
|
print("TOKENS:")
|
||||||
|
print(list.join(""))
|
428
addons/Wol/core/compiler/lexer.gd
Normal file
428
addons/Wol/core/compiler/lexer.gd
Normal file
|
@ -0,0 +1,428 @@
|
||||||
|
extends Object
|
||||||
|
|
||||||
|
const LINE_COMENT : String = "//"
|
||||||
|
const FORWARD_SLASH : String = "/"
|
||||||
|
|
||||||
|
const LINE_SEPARATOR : String = "\n"
|
||||||
|
|
||||||
|
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 : String = "\\s*"
|
||||||
|
|
||||||
|
var _states : Dictionary = {}
|
||||||
|
var _defaultState : LexerState
|
||||||
|
|
||||||
|
var _currentState : LexerState
|
||||||
|
|
||||||
|
var _indentStack : Array = []
|
||||||
|
var _shouldTrackIndent : bool = false
|
||||||
|
|
||||||
|
|
||||||
|
func _init():
|
||||||
|
create_states()
|
||||||
|
|
||||||
|
func create_states():
|
||||||
|
var patterns : Dictionary = {}
|
||||||
|
patterns[YarnGlobals.TokenType.Text] = ".*"
|
||||||
|
|
||||||
|
patterns[YarnGlobals.TokenType.Number] = "\\-?[0-9]+(\\.[0-9+])?"
|
||||||
|
patterns[YarnGlobals.TokenType.Str] = "\"([^\"\\\\]*(?:\\.[^\"\\\\]*)*)\""
|
||||||
|
patterns[YarnGlobals.TokenType.TagMarker] = "\\#"
|
||||||
|
patterns[YarnGlobals.TokenType.LeftParen] = "\\("
|
||||||
|
patterns[YarnGlobals.TokenType.RightParen] = "\\)"
|
||||||
|
patterns[YarnGlobals.TokenType.EqualTo] = "(==|is(?!\\w)|eq(?!\\w))"
|
||||||
|
patterns[YarnGlobals.TokenType.EqualToOrAssign] = "(=|to(?!\\w))"
|
||||||
|
patterns[YarnGlobals.TokenType.NotEqualTo] = "(\\!=|neq(?!\\w))"
|
||||||
|
patterns[YarnGlobals.TokenType.GreaterThanOrEqualTo] = "(\\>=|gte(?!\\w))"
|
||||||
|
patterns[YarnGlobals.TokenType.GreaterThan] = "(\\>|gt(?!\\w))"
|
||||||
|
patterns[YarnGlobals.TokenType.LessThanOrEqualTo] = "(\\<=|lte(?!\\w))"
|
||||||
|
patterns[YarnGlobals.TokenType.LessThan] = "(\\<|lt(?!\\w))"
|
||||||
|
patterns[YarnGlobals.TokenType.AddAssign] = "\\+="
|
||||||
|
patterns[YarnGlobals.TokenType.MinusAssign] = "\\-="
|
||||||
|
patterns[YarnGlobals.TokenType.MultiplyAssign] = "\\*="
|
||||||
|
patterns[YarnGlobals.TokenType.DivideAssign] = "\\/="
|
||||||
|
patterns[YarnGlobals.TokenType.Add] = "\\+"
|
||||||
|
patterns[YarnGlobals.TokenType.Minus] = "\\-"
|
||||||
|
patterns[YarnGlobals.TokenType.Multiply] = "\\*"
|
||||||
|
patterns[YarnGlobals.TokenType.Divide] = "\\/"
|
||||||
|
patterns[YarnGlobals.TokenType.Modulo] = "\\%"
|
||||||
|
patterns[YarnGlobals.TokenType.And] = "(\\&\\&|and(?!\\w))"
|
||||||
|
patterns[YarnGlobals.TokenType.Or] = "(\\|\\||or(?!\\w))"
|
||||||
|
patterns[YarnGlobals.TokenType.Xor] = "(\\^|xor(?!\\w))"
|
||||||
|
patterns[YarnGlobals.TokenType.Not] = "(\\!|not(?!\\w))"
|
||||||
|
patterns[YarnGlobals.TokenType.Variable] = "\\$([A-Za-z0-9_\\.])+"
|
||||||
|
patterns[YarnGlobals.TokenType.Comma] = "\\,"
|
||||||
|
patterns[YarnGlobals.TokenType.TrueToken] = "true(?!\\w)"
|
||||||
|
patterns[YarnGlobals.TokenType.FalseToken] = "false(?!\\w)"
|
||||||
|
patterns[YarnGlobals.TokenType.NullToken] = "null(?!\\w)"
|
||||||
|
patterns[YarnGlobals.TokenType.BeginCommand] = "\\<\\<"
|
||||||
|
patterns[YarnGlobals.TokenType.EndCommand] = "\\>\\>"
|
||||||
|
patterns[YarnGlobals.TokenType.OptionStart] = "\\[\\["
|
||||||
|
patterns[YarnGlobals.TokenType.OptionEnd] = "\\]\\]"
|
||||||
|
patterns[YarnGlobals.TokenType.OptionDelimit] = "\\|"
|
||||||
|
patterns[YarnGlobals.TokenType.Identifier] = "[a-zA-Z0-9_:\\.]+"
|
||||||
|
patterns[YarnGlobals.TokenType.IfToken] = "if(?!\\w)"
|
||||||
|
patterns[YarnGlobals.TokenType.ElseToken] = "else(?!\\w)"
|
||||||
|
patterns[YarnGlobals.TokenType.ElseIf] = "elseif(?!\\w)"
|
||||||
|
patterns[YarnGlobals.TokenType.EndIf] = "endif(?!\\w)"
|
||||||
|
patterns[YarnGlobals.TokenType.Set] = "set(?!\\w)"
|
||||||
|
patterns[YarnGlobals.TokenType.ShortcutOption] = "\\-\\>\\s*"
|
||||||
|
|
||||||
|
#compound states
|
||||||
|
var shortcut_option : String= SHORTCUT + DASH + OPTION
|
||||||
|
var shortcut_option_tag : String = shortcut_option + DASH + TAG
|
||||||
|
var command_or_expression : String= COMMAND + DASH + OR + DASH + EXPRESSION
|
||||||
|
var link_destination : String = LINK + DASH + DESTINATION
|
||||||
|
|
||||||
|
_states = {}
|
||||||
|
|
||||||
|
_states[BASE] = LexerState.new(patterns)
|
||||||
|
_states[BASE].add_transition(YarnGlobals.TokenType.BeginCommand,COMMAND,true)
|
||||||
|
_states[BASE].add_transition(YarnGlobals.TokenType.OptionStart,LINK,true)
|
||||||
|
_states[BASE].add_transition(YarnGlobals.TokenType.ShortcutOption,shortcut_option)
|
||||||
|
_states[BASE].add_transition(YarnGlobals.TokenType.TagMarker,TAG,true)
|
||||||
|
_states[BASE].add_text_rule(YarnGlobals.TokenType.Text)
|
||||||
|
|
||||||
|
_states[TAG] = LexerState.new(patterns)
|
||||||
|
_states[TAG].add_transition(YarnGlobals.TokenType.Identifier,BASE)
|
||||||
|
|
||||||
|
_states[shortcut_option] = LexerState.new(patterns)
|
||||||
|
_states[shortcut_option].track_indent = true
|
||||||
|
_states[shortcut_option].add_transition(YarnGlobals.TokenType.BeginCommand,EXPRESSION,true)
|
||||||
|
_states[shortcut_option].add_transition(YarnGlobals.TokenType.TagMarker,shortcut_option_tag,true)
|
||||||
|
_states[shortcut_option].add_text_rule(YarnGlobals.TokenType.Text,BASE)
|
||||||
|
|
||||||
|
_states[shortcut_option_tag] = LexerState.new(patterns)
|
||||||
|
_states[shortcut_option_tag].add_transition(YarnGlobals.TokenType.Identifier,shortcut_option)
|
||||||
|
|
||||||
|
_states[COMMAND] = LexerState.new(patterns)
|
||||||
|
_states[COMMAND].add_transition(YarnGlobals.TokenType.IfToken,EXPRESSION)
|
||||||
|
_states[COMMAND].add_transition(YarnGlobals.TokenType.ElseToken)
|
||||||
|
_states[COMMAND].add_transition(YarnGlobals.TokenType.ElseIf,EXPRESSION)
|
||||||
|
_states[COMMAND].add_transition(YarnGlobals.TokenType.EndIf)
|
||||||
|
_states[COMMAND].add_transition(YarnGlobals.TokenType.Set,ASSIGNMENT)
|
||||||
|
_states[COMMAND].add_transition(YarnGlobals.TokenType.EndCommand,BASE,true)
|
||||||
|
_states[COMMAND].add_transition(YarnGlobals.TokenType.Identifier,command_or_expression)
|
||||||
|
_states[COMMAND].add_text_rule(YarnGlobals.TokenType.Text)
|
||||||
|
|
||||||
|
_states[command_or_expression] = LexerState.new(patterns)
|
||||||
|
_states[command_or_expression].add_transition(YarnGlobals.TokenType.LeftParen,EXPRESSION)
|
||||||
|
_states[command_or_expression].add_transition(YarnGlobals.TokenType.EndCommand,BASE,true)
|
||||||
|
_states[command_or_expression].add_text_rule(YarnGlobals.TokenType.Text)
|
||||||
|
|
||||||
|
_states[ASSIGNMENT] = LexerState.new(patterns)
|
||||||
|
_states[ASSIGNMENT].add_transition(YarnGlobals.TokenType.Variable)
|
||||||
|
_states[ASSIGNMENT].add_transition(YarnGlobals.TokenType.EqualToOrAssign, EXPRESSION)
|
||||||
|
_states[ASSIGNMENT].add_transition(YarnGlobals.TokenType.AddAssign, EXPRESSION)
|
||||||
|
_states[ASSIGNMENT].add_transition(YarnGlobals.TokenType.MinusAssign, EXPRESSION)
|
||||||
|
_states[ASSIGNMENT].add_transition(YarnGlobals.TokenType.MultiplyAssign, EXPRESSION)
|
||||||
|
_states[ASSIGNMENT].add_transition(YarnGlobals.TokenType.DivideAssign, EXPRESSION)
|
||||||
|
|
||||||
|
_states[EXPRESSION] = LexerState.new(patterns)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.EndCommand, BASE)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.Number)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.Str)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.LeftParen)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.RightParen)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.EqualTo)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.EqualToOrAssign)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.NotEqualTo)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.GreaterThanOrEqualTo)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.GreaterThan)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.LessThanOrEqualTo)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.LessThan)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.Add)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.Minus)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.Multiply)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.Divide)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.Modulo)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.And)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.Or)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.Xor)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.Not)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.Variable)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.Comma)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.TrueToken)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.FalseToken)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.NullToken)
|
||||||
|
_states[EXPRESSION].add_transition(YarnGlobals.TokenType.Identifier)
|
||||||
|
|
||||||
|
_states[LINK] = LexerState.new(patterns)
|
||||||
|
_states[LINK].add_transition(YarnGlobals.TokenType.OptionEnd, BASE, true)
|
||||||
|
_states[LINK].add_transition(YarnGlobals.TokenType.OptionDelimit, link_destination, true)
|
||||||
|
_states[LINK].add_text_rule(YarnGlobals.TokenType.Text)
|
||||||
|
|
||||||
|
_states[link_destination] = LexerState.new(patterns)
|
||||||
|
_states[link_destination].add_transition(YarnGlobals.TokenType.Identifier)
|
||||||
|
_states[link_destination].add_transition(YarnGlobals.TokenType.OptionEnd, BASE)
|
||||||
|
|
||||||
|
_defaultState = _states[BASE]
|
||||||
|
|
||||||
|
for stateKey in _states.keys():
|
||||||
|
_states[stateKey].stateName = stateKey
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
func tokenize(text:String)->Array:
|
||||||
|
|
||||||
|
_indentStack.clear()
|
||||||
|
_indentStack.push_front(IntBoolPair.new(0,false))
|
||||||
|
_shouldTrackIndent = false
|
||||||
|
|
||||||
|
var tokens : Array = []
|
||||||
|
|
||||||
|
_currentState = _defaultState
|
||||||
|
|
||||||
|
var lines : PoolStringArray = text.split(LINE_SEPARATOR)
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
var lineNumber : int = 1
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
tokens+=tokenize_line(line,lineNumber)
|
||||||
|
lineNumber+=1
|
||||||
|
|
||||||
|
var endOfInput : Token = Token.new(YarnGlobals.TokenType.EndOfInput,_currentState,lineNumber,0)
|
||||||
|
tokens.append(endOfInput)
|
||||||
|
|
||||||
|
# print(tokens)
|
||||||
|
|
||||||
|
return tokens
|
||||||
|
|
||||||
|
func tokenize_line(line:String, lineNumber : int)->Array:
|
||||||
|
var tokenStack : Array = []
|
||||||
|
|
||||||
|
var freshLine = line.replace("\t"," ").replace("\r","")
|
||||||
|
|
||||||
|
#record indentation
|
||||||
|
var indentation = line_indentation(line)
|
||||||
|
var prevIndentation : IntBoolPair = _indentStack.front()
|
||||||
|
|
||||||
|
if _shouldTrackIndent && indentation > prevIndentation.key:
|
||||||
|
#we add an indenation token to record indent level
|
||||||
|
_indentStack.push_front(IntBoolPair.new(indentation,true))
|
||||||
|
|
||||||
|
var indent : Token = Token.new(YarnGlobals.TokenType.Indent,_currentState,lineNumber,prevIndentation.key)
|
||||||
|
indent.value = "%*s" % [indentation - prevIndentation.key,""]
|
||||||
|
|
||||||
|
_shouldTrackIndent = false
|
||||||
|
tokenStack.push_front(indent)
|
||||||
|
|
||||||
|
elif indentation < prevIndentation.key:
|
||||||
|
#de-indent and then emit indentaiton token
|
||||||
|
|
||||||
|
while indentation < _indentStack.front().key:
|
||||||
|
var top : IntBoolPair = _indentStack.pop_front()
|
||||||
|
if top.value:
|
||||||
|
var deIndent : Token = Token.new(YarnGlobals.TokenType.Dedent,_currentState,lineNumber,0)
|
||||||
|
tokenStack.push_front(deIndent)
|
||||||
|
|
||||||
|
|
||||||
|
var column : int = indentation
|
||||||
|
|
||||||
|
var whitespace : RegEx = RegEx.new()
|
||||||
|
var error = whitespace.compile(WHITESPACE)
|
||||||
|
if error != OK:
|
||||||
|
printerr("unable to compile regex WHITESPACE")
|
||||||
|
return []
|
||||||
|
|
||||||
|
while column < freshLine.length():
|
||||||
|
|
||||||
|
if freshLine.substr(column).begins_with(LINE_COMENT):
|
||||||
|
break
|
||||||
|
|
||||||
|
var matched : bool = false
|
||||||
|
|
||||||
|
for rule in _currentState.rules:
|
||||||
|
var found : RegExMatch = rule.regex.search(freshLine, column)
|
||||||
|
|
||||||
|
if !found:
|
||||||
|
continue
|
||||||
|
|
||||||
|
var tokenText : String
|
||||||
|
|
||||||
|
if rule.tokenType == YarnGlobals.TokenType.Text:
|
||||||
|
#if this is text then we back up to the most recent
|
||||||
|
#delimiting token and treat everything from there as text.
|
||||||
|
|
||||||
|
var startIndex : int = indentation
|
||||||
|
|
||||||
|
if tokenStack.size() > 0 :
|
||||||
|
while tokenStack.front().type == YarnGlobals.TokenType.Identifier:
|
||||||
|
tokenStack.pop_front()
|
||||||
|
|
||||||
|
var startDelimitToken : Token = tokenStack.front()
|
||||||
|
startIndex = startDelimitToken.column
|
||||||
|
|
||||||
|
if startDelimitToken.type == YarnGlobals.TokenType.Indent:
|
||||||
|
startIndex += startDelimitToken.value.length()
|
||||||
|
if startDelimitToken.type == YarnGlobals.TokenType.Dedent:
|
||||||
|
startIndex = indentation
|
||||||
|
#
|
||||||
|
|
||||||
|
column = startIndex
|
||||||
|
var endIndex : int = found.get_start() + found.get_string().length()
|
||||||
|
|
||||||
|
tokenText = freshLine.substr(startIndex,endIndex-startIndex)
|
||||||
|
|
||||||
|
else:
|
||||||
|
tokenText = found.get_string()
|
||||||
|
|
||||||
|
column += tokenText.length()
|
||||||
|
|
||||||
|
#pre-proccess string
|
||||||
|
if rule.tokenType == YarnGlobals.TokenType.Str:
|
||||||
|
tokenText = tokenText.substr(1,tokenText.length() - 2)
|
||||||
|
tokenText = tokenText.replace("\\\\", "\\")
|
||||||
|
tokenText = tokenText.replace("\\\"","\"")
|
||||||
|
|
||||||
|
var token : Token = Token.new(rule.tokenType,_currentState,lineNumber,column,tokenText)
|
||||||
|
token.delimitsText = rule.delimitsText
|
||||||
|
|
||||||
|
tokenStack.push_front(token)
|
||||||
|
|
||||||
|
if rule.enterState != null && rule.enterState.length() > 0:
|
||||||
|
|
||||||
|
if !_states.has(rule.enterState):
|
||||||
|
printerr("State[%s] not known - line(%s) col(%s)"%[rule.enterState,lineNumber,column])
|
||||||
|
return []
|
||||||
|
|
||||||
|
enter_state(_states[rule.enterState])
|
||||||
|
|
||||||
|
if _shouldTrackIndent:
|
||||||
|
if _indentStack.front().key < indentation:
|
||||||
|
_indentStack.append(IntBoolPair.new(indentation,false))
|
||||||
|
|
||||||
|
matched = true
|
||||||
|
break
|
||||||
|
|
||||||
|
if !matched:
|
||||||
|
# TODO: Send out some helpful messages
|
||||||
|
printerr("expectedTokens [%s] - line(%s) col(%s)"%["refineErrors.Lexer.tokenize_line",lineNumber,column])
|
||||||
|
return []
|
||||||
|
|
||||||
|
var lastWhiteSpace : RegExMatch = whitespace.search(line,column)
|
||||||
|
if lastWhiteSpace:
|
||||||
|
column += lastWhiteSpace.get_string().length()
|
||||||
|
|
||||||
|
|
||||||
|
tokenStack.invert()
|
||||||
|
|
||||||
|
return tokenStack
|
||||||
|
|
||||||
|
func line_indentation(line:String)->int:
|
||||||
|
var indentRegex : RegEx = RegEx.new()
|
||||||
|
indentRegex.compile("^(\\s*)")
|
||||||
|
|
||||||
|
var found : RegExMatch = indentRegex.search(line)
|
||||||
|
|
||||||
|
if !found || found.get_string().length() <= 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
return found.get_string().length()
|
||||||
|
|
||||||
|
func enter_state(state:LexerState):
|
||||||
|
_currentState = state;
|
||||||
|
if _currentState.track_indent:
|
||||||
|
_shouldTrackIndent = true
|
||||||
|
|
||||||
|
class Token:
|
||||||
|
var type : int
|
||||||
|
var value : String
|
||||||
|
|
||||||
|
var lineNumber : int
|
||||||
|
var column : int
|
||||||
|
var text : String
|
||||||
|
|
||||||
|
var delimitsText : bool= false
|
||||||
|
var paramCount : int
|
||||||
|
var lexerState : String
|
||||||
|
|
||||||
|
func _init(type:int,state: LexerState, lineNumber:int = -1,column:int = -1,value:String =""):
|
||||||
|
self.type = type
|
||||||
|
self.lexerState = state.stateName
|
||||||
|
self.lineNumber = lineNumber
|
||||||
|
self.column = column
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
func _to_string():
|
||||||
|
return "%s (%s) at %s:%s (state: %s)" % [YarnGlobals.token_type_name(type),value,lineNumber,column,lexerState]
|
||||||
|
|
||||||
|
|
||||||
|
class LexerState:
|
||||||
|
|
||||||
|
var stateName : String
|
||||||
|
var patterns : Dictionary
|
||||||
|
var rules : Array = []
|
||||||
|
var track_indent : bool = false
|
||||||
|
|
||||||
|
func _init(patterns):
|
||||||
|
self.patterns = patterns
|
||||||
|
|
||||||
|
func add_transition(type : int, state : String = "",delimitText : bool = false)->Rule:
|
||||||
|
var pattern = "\\G%s" % patterns[type]
|
||||||
|
# print("pattern = %s" % pattern)
|
||||||
|
var rule = Rule.new(type,pattern,state,delimitText)
|
||||||
|
rules.append(rule)
|
||||||
|
return rule
|
||||||
|
|
||||||
|
func add_text_rule(type : int, state : String = "")->Rule:
|
||||||
|
if contains_text_rule() :
|
||||||
|
printerr("State already contains Text rule")
|
||||||
|
return null
|
||||||
|
|
||||||
|
var delimiters:Array = []
|
||||||
|
for rule in rules:
|
||||||
|
if rule.delimitsText:
|
||||||
|
delimiters.append("%s" % rule.regex.get_pattern().substr(2))
|
||||||
|
|
||||||
|
var pattern = "\\G((?!%s).)*" % [PoolStringArray(delimiters).join("|")]
|
||||||
|
var rule : Rule = add_transition(type,state)
|
||||||
|
rule.regex = RegEx.new()
|
||||||
|
rule.regex.compile(pattern)
|
||||||
|
rule.isTextRule = true
|
||||||
|
return rule
|
||||||
|
|
||||||
|
func contains_text_rule()->bool:
|
||||||
|
for rule in rules:
|
||||||
|
if rule.isTextRule:
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
class Rule:
|
||||||
|
var regex : RegEx
|
||||||
|
|
||||||
|
var enterState : String
|
||||||
|
var tokenType : int
|
||||||
|
var isTextRule : bool
|
||||||
|
var delimitsText : bool
|
||||||
|
|
||||||
|
func _init(type : int , regex : String, enterState : String, delimitsText:bool):
|
||||||
|
self.tokenType = type
|
||||||
|
self.regex = RegEx.new()
|
||||||
|
self.regex.compile(regex)
|
||||||
|
self.enterState = enterState
|
||||||
|
self.delimitsText = delimitsText
|
||||||
|
|
||||||
|
func _to_string():
|
||||||
|
return "[Rule : %s - %s]" % [YarnGlobals.token_type_name(tokenType),regex]
|
||||||
|
|
||||||
|
class IntBoolPair:
|
||||||
|
var key : int
|
||||||
|
var value : bool
|
||||||
|
|
||||||
|
func _init(key:int,value:bool):
|
||||||
|
self.key = key
|
||||||
|
self.value = value
|
||||||
|
|
961
addons/Wol/core/compiler/parser.gd
Normal file
961
addons/Wol/core/compiler/parser.gd
Normal file
|
@ -0,0 +1,961 @@
|
||||||
|
extends Object
|
||||||
|
|
||||||
|
const YarnGlobals = preload("res://addons/Wol/autoloads/execution_states.gd")
|
||||||
|
const Lexer = preload("res://addons/Wol/core/compiler/lexer.gd")
|
||||||
|
|
||||||
|
|
||||||
|
var _tokens : Array = []#token
|
||||||
|
|
||||||
|
func _init(tokens):
|
||||||
|
self._tokens = tokens
|
||||||
|
|
||||||
|
#how to handle operations
|
||||||
|
enum Associativity {
|
||||||
|
Left,Right,None
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse_node()->YarnNode:
|
||||||
|
return YarnNode.new("Start",null,self)
|
||||||
|
|
||||||
|
func next_symbol_is(validTypes:Array)->bool:
|
||||||
|
var type = self._tokens.front().type
|
||||||
|
for validType in validTypes:
|
||||||
|
if type == validType:
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
#look ahead for `<<` and `else`
|
||||||
|
func next_symbols_are(validTypes:Array)->bool:
|
||||||
|
var temp = []+_tokens
|
||||||
|
for type in validTypes:
|
||||||
|
if temp.pop_front().type != type:
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
func expect_symbol(tokenTypes:Array = [])->Lexer.Token:
|
||||||
|
var t = self._tokens.pop_front() as Lexer.Token
|
||||||
|
var size = tokenTypes.size()
|
||||||
|
|
||||||
|
if size == 0:
|
||||||
|
if t.type == YarnGlobals.TokenType.EndOfInput:
|
||||||
|
printerr("unexpected end of input")
|
||||||
|
return null
|
||||||
|
return t
|
||||||
|
|
||||||
|
for type in tokenTypes:
|
||||||
|
if t.type == type:
|
||||||
|
return t
|
||||||
|
|
||||||
|
printerr("unexpexted token: expected[ %s ] but got [ %s ]"% (tokenTypes+[t.type]))
|
||||||
|
return null
|
||||||
|
|
||||||
|
static func tab(indentLevel : int , input : String,newLine : bool = true)->String:
|
||||||
|
return ("%*s| %s%s"% [indentLevel*2,"",input,("" if !newLine else "\n")])
|
||||||
|
|
||||||
|
func tokens()->Array:
|
||||||
|
return _tokens
|
||||||
|
|
||||||
|
class ParseNode:
|
||||||
|
var parent : ParseNode
|
||||||
|
var lineNumber : int
|
||||||
|
var tags : Array#<String>
|
||||||
|
|
||||||
|
func _init(parent:ParseNode,parser):
|
||||||
|
self.parent = parent
|
||||||
|
var tokens : Array = parser.tokens() as Array
|
||||||
|
if tokens.size() > 0:
|
||||||
|
lineNumber = tokens.front().lineNumber
|
||||||
|
else:
|
||||||
|
lineNumber = -1
|
||||||
|
tags = []
|
||||||
|
|
||||||
|
func tree_string(indentLevel : int)->String:
|
||||||
|
return "NotImplemented"
|
||||||
|
|
||||||
|
func tags_to_string(indentLevel : int)->String:
|
||||||
|
return "%s" % "TAGS<tags_to_string>NOTIMPLEMENTED"
|
||||||
|
|
||||||
|
func get_node_parent()->YarnNode:
|
||||||
|
var node = self
|
||||||
|
while node != null:
|
||||||
|
if node.has_method("yarn_node"):
|
||||||
|
return node as YarnNode
|
||||||
|
node = node.parent
|
||||||
|
return null
|
||||||
|
|
||||||
|
func tab(indentLevel : int , input : String,newLine : bool = true)->String:
|
||||||
|
return ("%*s| %s%s"% [indentLevel*2,"",input,("" if !newLine else "\n")])
|
||||||
|
|
||||||
|
|
||||||
|
func set_parent(parent):
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
#this is a Yarn Node - contains all the text
|
||||||
|
class YarnNode extends ParseNode:
|
||||||
|
|
||||||
|
var name : String
|
||||||
|
var source : String
|
||||||
|
|
||||||
|
var editorNodeTags : Array =[]#tags defined in node header
|
||||||
|
var statements : Array = []# Statement
|
||||||
|
|
||||||
|
func _init(name:String,parent:ParseNode,parser).(parent,parser):
|
||||||
|
|
||||||
|
self.name = name
|
||||||
|
while (parser.tokens().size() > 0 &&
|
||||||
|
!parser.next_symbol_is([YarnGlobals.TokenType.Dedent,YarnGlobals.TokenType.EndOfInput])):
|
||||||
|
statements.append(Statement.new(self,parser))
|
||||||
|
#print(statements.size())
|
||||||
|
|
||||||
|
func yarn_node():
|
||||||
|
pass
|
||||||
|
|
||||||
|
func tree_string(indentLevel : int)->String:
|
||||||
|
|
||||||
|
var info : PoolStringArray = []
|
||||||
|
|
||||||
|
for statement in statements:
|
||||||
|
info.append(statement.tree_string(indentLevel +1))
|
||||||
|
|
||||||
|
#print("printing TREEEEEEEEEEEEE")
|
||||||
|
|
||||||
|
return info.join("")
|
||||||
|
|
||||||
|
|
||||||
|
class Header extends ParseNode:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Statement extends ParseNode:
|
||||||
|
var Type = YarnGlobals.StatementTypes
|
||||||
|
|
||||||
|
var type : int
|
||||||
|
var block : Block
|
||||||
|
var ifStatement : IfStatement
|
||||||
|
var optionStatement : OptionStatement
|
||||||
|
var assignment : Assignment
|
||||||
|
var shortcutOptionGroup : ShortcutOptionGroup
|
||||||
|
var customCommand : CustomCommand
|
||||||
|
var line : String
|
||||||
|
|
||||||
|
func _init(parent:ParseNode,parser).(parent,parser):
|
||||||
|
|
||||||
|
if Block.can_parse(parser):
|
||||||
|
block = Block.new(self,parser)
|
||||||
|
type = Type.Block
|
||||||
|
elif IfStatement.can_parse(parser):
|
||||||
|
ifStatement = IfStatement.new(self,parser)
|
||||||
|
type = Type.IfStatement
|
||||||
|
elif OptionStatement.can_parse(parser):
|
||||||
|
optionStatement = OptionStatement.new(self,parser)
|
||||||
|
type = Type.OptionStatement
|
||||||
|
elif Assignment.can_parse(parser):
|
||||||
|
assignment = Assignment.new(self,parser)
|
||||||
|
type = Type.AssignmentStatement
|
||||||
|
elif ShortcutOptionGroup.can_parse(parser):
|
||||||
|
shortcutOptionGroup = ShortcutOptionGroup.new(self,parser)
|
||||||
|
type = Type.ShortcutOptionGroup
|
||||||
|
elif CustomCommand.can_parse(parser):
|
||||||
|
customCommand = CustomCommand.new(self,parser)
|
||||||
|
type = Type.CustomCommand
|
||||||
|
elif parser.next_symbol_is([YarnGlobals.TokenType.Text]):
|
||||||
|
line = parser.expect_symbol([YarnGlobals.TokenType.Text]).value
|
||||||
|
type = Type.Line
|
||||||
|
else:
|
||||||
|
printerr("expected a statement but got %s instead. (probably an inbalanced if statement)" % parser.tokens().front()._to_string())
|
||||||
|
|
||||||
|
|
||||||
|
var tags : Array = []
|
||||||
|
|
||||||
|
while parser.next_symbol_is([YarnGlobals.TokenType.TagMarker]):
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.TagMarker])
|
||||||
|
var tag : String = parser.expect_symbol([YarnGlobals.TokenType.Identifier]).value
|
||||||
|
tags.append(tag)
|
||||||
|
|
||||||
|
if(tags.size()>0):
|
||||||
|
self.tags = tags
|
||||||
|
|
||||||
|
func tree_string(indentLevel : int)->String:
|
||||||
|
var info : PoolStringArray = []
|
||||||
|
|
||||||
|
match type :
|
||||||
|
Type.Block:
|
||||||
|
info.append(block.tree_string(indentLevel))
|
||||||
|
Type.IfStatement:
|
||||||
|
info.append(ifStatement.tree_string(indentLevel))
|
||||||
|
Type.AssignmentStatement:
|
||||||
|
info.append(assignment.tree_string(indentLevel))
|
||||||
|
Type.OptionStatement:
|
||||||
|
info.append(optionStatement.tree_string(indentLevel))
|
||||||
|
Type.ShortcutOptionGroup:
|
||||||
|
info.append(shortcutOptionGroup.tree_string(indentLevel))
|
||||||
|
Type.CustomCommand:
|
||||||
|
info.append(customCommand.tree_string(indentLevel))
|
||||||
|
Type.Line:
|
||||||
|
info.append(tab(indentLevel,"Line: %s"%line))
|
||||||
|
_:
|
||||||
|
printerr("cannot print statement")
|
||||||
|
|
||||||
|
#print("statement --")
|
||||||
|
|
||||||
|
return info.join("")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CustomCommand extends ParseNode:
|
||||||
|
|
||||||
|
enum Type {
|
||||||
|
Expression,ClientCommand
|
||||||
|
}
|
||||||
|
|
||||||
|
var type : int
|
||||||
|
var expression : ExpressionNode
|
||||||
|
var clientCommand : String
|
||||||
|
|
||||||
|
func _init(parent:ParseNode,parser).(parent,parser):
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.BeginCommand])
|
||||||
|
|
||||||
|
var commandTokens = []
|
||||||
|
commandTokens.append(parser.expect_symbol())
|
||||||
|
|
||||||
|
while !parser.next_symbol_is([YarnGlobals.TokenType.EndCommand]):
|
||||||
|
commandTokens.append(parser.expect_symbol())
|
||||||
|
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.EndCommand])
|
||||||
|
|
||||||
|
#if first token is identifier and second is leftt parenthesis
|
||||||
|
#evaluate as function
|
||||||
|
if (commandTokens.size() > 1 && commandTokens[0].type == YarnGlobals.TokenType.Identifier
|
||||||
|
&& commandTokens[1].type == YarnGlobals.TokenType.LeftParen):
|
||||||
|
var p = get_script().new(commandTokens,parser.library)
|
||||||
|
var expression : ExpressionNode = ExpressionNode.parse(self,p)
|
||||||
|
type = Type.Expression
|
||||||
|
self.expression = expression
|
||||||
|
else:
|
||||||
|
#otherwise evaluuate command
|
||||||
|
type = Type.ClientCommand
|
||||||
|
self.clientCommand = commandTokens[0].value
|
||||||
|
|
||||||
|
func tree_string(indentLevel : int)->String:
|
||||||
|
match type:
|
||||||
|
Type.Expression:
|
||||||
|
return tab(indentLevel,"Expression: %s"% expression.tree_string(indentLevel+1))
|
||||||
|
Type.ClientCommand:
|
||||||
|
return tab(indentLevel,"Command: %s"%clientCommand)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
static func can_parse(parser)->bool:
|
||||||
|
return (parser.next_symbols_are([YarnGlobals.TokenType.BeginCommand,YarnGlobals.TokenType.Text])
|
||||||
|
|| parser.next_symbols_are([YarnGlobals.TokenType.BeginCommand,YarnGlobals.TokenType.Identifier]))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ShortcutOptionGroup extends ParseNode:
|
||||||
|
|
||||||
|
var options : Array = []#ShortcutOptions
|
||||||
|
|
||||||
|
func _init(parent:ParseNode,parser).(parent,parser):
|
||||||
|
|
||||||
|
# parse options until there is no more
|
||||||
|
# expect one otherwise invalid
|
||||||
|
|
||||||
|
var sIndex : int = 1
|
||||||
|
options.append(ShortCutOption.new(sIndex, self, parser))
|
||||||
|
sIndex+=1
|
||||||
|
while parser.next_symbol_is([YarnGlobals.TokenType.ShortcutOption]):
|
||||||
|
options.append(ShortCutOption.new(sIndex, self, parser))
|
||||||
|
sIndex+=1
|
||||||
|
|
||||||
|
|
||||||
|
func tree_string(indentLevel : int)->String:
|
||||||
|
var info : PoolStringArray = []
|
||||||
|
|
||||||
|
info.append(tab(indentLevel,"Shortcut Option Group{"))
|
||||||
|
|
||||||
|
for option in options:
|
||||||
|
info.append(option.tree_string(indentLevel+1))
|
||||||
|
|
||||||
|
info.append(tab(indentLevel,"}"))
|
||||||
|
|
||||||
|
return info.join("")
|
||||||
|
|
||||||
|
static func can_parse(parser)->bool:
|
||||||
|
return parser.next_symbol_is([YarnGlobals.TokenType.ShortcutOption])
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ShortCutOption extends ParseNode:
|
||||||
|
|
||||||
|
var label : String
|
||||||
|
var condition : ExpressionNode
|
||||||
|
var node : YarnNode
|
||||||
|
|
||||||
|
func _init(index:int, parent:ParseNode, parser).(parent,parser):
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.ShortcutOption])
|
||||||
|
label = parser.expect_symbol([YarnGlobals.TokenType.Text]).value
|
||||||
|
|
||||||
|
# parse the conditional << if $x >> when it exists
|
||||||
|
|
||||||
|
var tags : Array = []#string
|
||||||
|
while( parser.next_symbols_are([YarnGlobals.TokenType.BeginCommand,YarnGlobals.TokenType.IfToken])
|
||||||
|
|| parser.next_symbol_is([YarnGlobals.TokenType.TagMarker])):
|
||||||
|
|
||||||
|
if parser.next_symbols_are([YarnGlobals.TokenType.BeginCommand, YarnGlobals.TokenType.IfToken]):
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.BeginCommand])
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.IfToken])
|
||||||
|
condition = ExpressionNode.parse(self,parser)
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.EndCommand])
|
||||||
|
elif parser.next_symbol_is([YarnGlobals.TokenType.TagMarker]):
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.TagMarker])
|
||||||
|
var tag : String = parser.expect_symbol([YarnGlobals.TokenType.Identifier]).value;
|
||||||
|
tags.append(tag)
|
||||||
|
|
||||||
|
|
||||||
|
self.tags = tags
|
||||||
|
# parse remaining statements
|
||||||
|
|
||||||
|
if parser.next_symbol_is([YarnGlobals.TokenType.Indent]):
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.Indent])
|
||||||
|
node = YarnNode.new("%s.%s" %[self.get_node_parent().name ,index], self,parser)
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.Dedent])
|
||||||
|
|
||||||
|
|
||||||
|
func tree_string(indentLevel : int)->String:
|
||||||
|
var info : PoolStringArray = []
|
||||||
|
|
||||||
|
info.append(tab(indentLevel,"Option \"%s\""%label))
|
||||||
|
|
||||||
|
if condition != null :
|
||||||
|
info.append(tab(indentLevel+1,"(when:"))
|
||||||
|
info.append(condition.tree_string(indentLevel+2))
|
||||||
|
info.append(tab(indentLevel+1,"),"))
|
||||||
|
if node != null:
|
||||||
|
info.append(tab(indentLevel, "{"))
|
||||||
|
info.append(node.tree_string(indentLevel + 1));
|
||||||
|
info.append(tab(indentLevel, "}"));
|
||||||
|
|
||||||
|
return info.join("")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#Blocks are groups of statements with the same indent level
|
||||||
|
class Block extends ParseNode:
|
||||||
|
|
||||||
|
var statements : Array = []
|
||||||
|
|
||||||
|
func _init(parent:ParseNode, parser).(parent,parser):
|
||||||
|
#read indent
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.Indent])
|
||||||
|
|
||||||
|
#keep reading statements until we hit a dedent
|
||||||
|
while !parser.next_symbol_is([YarnGlobals.TokenType.Dedent]):
|
||||||
|
#parse all statements including nested blocks
|
||||||
|
statements.append(Statement.new(self,parser))
|
||||||
|
|
||||||
|
#clean up dedent
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.Dedent])
|
||||||
|
|
||||||
|
|
||||||
|
func tree_string(indentLevel : int)->String:
|
||||||
|
var info : PoolStringArray = []
|
||||||
|
|
||||||
|
info.append(tab(indentLevel,"Block {"))
|
||||||
|
|
||||||
|
for statement in statements:
|
||||||
|
info.append(statement.tree_string(indentLevel+1))
|
||||||
|
|
||||||
|
info.append(tab(indentLevel,"}"))
|
||||||
|
|
||||||
|
return info.join("")
|
||||||
|
|
||||||
|
static func can_parse(parser)->bool:
|
||||||
|
return parser.next_symbol_is([YarnGlobals.TokenType.Indent])
|
||||||
|
|
||||||
|
#Option Statements are links to other nodes
|
||||||
|
class OptionStatement extends ParseNode:
|
||||||
|
|
||||||
|
var destination : String
|
||||||
|
var label : String
|
||||||
|
|
||||||
|
func _init(parent:ParseNode, parser).(parent,parser):
|
||||||
|
|
||||||
|
var strings : Array = []#string
|
||||||
|
|
||||||
|
#parse [[LABEL
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.OptionStart])
|
||||||
|
strings.append(parser.expect_symbol([YarnGlobals.TokenType.Text]).value)
|
||||||
|
|
||||||
|
#if there is a | get the next string
|
||||||
|
if parser.next_symbol_is([YarnGlobals.TokenType.OptionDelimit]):
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.OptionDelimit])
|
||||||
|
var t = parser.expect_symbol([YarnGlobals.TokenType.Text,YarnGlobals.TokenType.Identifier])
|
||||||
|
#print("Token %s"%t.value)
|
||||||
|
strings.append(t.value as String)
|
||||||
|
|
||||||
|
label = strings[0] if strings.size() > 1 else ""
|
||||||
|
destination = strings[1] if strings.size() > 1 else strings[0]
|
||||||
|
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.OptionEnd])
|
||||||
|
|
||||||
|
func tree_string(indentLevel : int)->String:
|
||||||
|
if label != null:
|
||||||
|
return tab(indentLevel,"Option: %s -> %s"%[label,destination])
|
||||||
|
else:
|
||||||
|
return tab(indentLevel,"Option: -> %s"%destination)
|
||||||
|
|
||||||
|
static func can_parse(parser)->bool:
|
||||||
|
return parser.next_symbol_is([YarnGlobals.TokenType.OptionStart])
|
||||||
|
|
||||||
|
class IfStatement extends ParseNode:
|
||||||
|
|
||||||
|
var clauses : Array = []#Clauses
|
||||||
|
|
||||||
|
func _init(parent:ParseNode, parser).(parent,parser):
|
||||||
|
|
||||||
|
#<<if Expression>>
|
||||||
|
var prime : Clause = Clause.new()
|
||||||
|
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.BeginCommand])
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.IfToken])
|
||||||
|
prime.expression = ExpressionNode.parse(self,parser)
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.EndCommand])
|
||||||
|
|
||||||
|
#read statements until 'endif' or 'else' or 'else if'
|
||||||
|
var statements : Array = []#statement
|
||||||
|
while (!parser.next_symbols_are([YarnGlobals.TokenType.BeginCommand, YarnGlobals.TokenType.EndIf])
|
||||||
|
&& !parser.next_symbols_are([YarnGlobals.TokenType.BeginCommand, YarnGlobals.TokenType.ElseToken])
|
||||||
|
&& !parser.next_symbols_are([YarnGlobals.TokenType.BeginCommand, YarnGlobals.TokenType.ElseIf])):
|
||||||
|
|
||||||
|
statements.append(Statement.new(self,parser))
|
||||||
|
|
||||||
|
#ignore dedent
|
||||||
|
while parser.next_symbol_is([YarnGlobals.TokenType.Dedent]):
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.Dedent])
|
||||||
|
|
||||||
|
|
||||||
|
prime.statements = statements
|
||||||
|
clauses.append(prime)
|
||||||
|
|
||||||
|
#handle all else if
|
||||||
|
while parser.next_symbols_are([YarnGlobals.TokenType.BeginCommand,YarnGlobals.TokenType.ElseIf]):
|
||||||
|
var clauseElif : Clause = Clause.new()
|
||||||
|
|
||||||
|
#parse condition syntax
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.BeginCommand])
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.ElseIf])
|
||||||
|
clauseElif.expression = ExpressionNode.parse(self,parser)
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.EndCommand])
|
||||||
|
|
||||||
|
|
||||||
|
var elifStatements : Array = []#statement
|
||||||
|
while (!parser.next_symbols_are([YarnGlobals.TokenType.BeginCommand, YarnGlobals.TokenType.EndIf])
|
||||||
|
&& !parser.next_symbols_are([YarnGlobals.TokenType.BeginCommand, YarnGlobals.TokenType.ElseToken])
|
||||||
|
&& !parser.next_symbols_are([YarnGlobals.TokenType.BeginCommand, YarnGlobals.TokenType.ElseIf])):
|
||||||
|
|
||||||
|
elifStatements.append(Statement.new(self,parser))
|
||||||
|
|
||||||
|
#ignore dedent
|
||||||
|
while parser.next_symbol_is([YarnGlobals.TokenType.Dedent]):
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.Dedent])
|
||||||
|
|
||||||
|
|
||||||
|
clauseElif.statements = statements
|
||||||
|
clauses.append(clauseElif)
|
||||||
|
|
||||||
|
#handle else if exists
|
||||||
|
if (parser.next_symbols_are([YarnGlobals.TokenType.BeginCommand,
|
||||||
|
YarnGlobals.TokenType.ElseToken,YarnGlobals.TokenType.EndCommand])):
|
||||||
|
|
||||||
|
#expect no expression - just <<else>>
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.BeginCommand])
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.ElseToken])
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.EndCommand])
|
||||||
|
|
||||||
|
#parse until hit endif
|
||||||
|
var clauseElse : Clause = Clause.new()
|
||||||
|
var elStatements : Array = []#statement
|
||||||
|
while !parser.next_symbols_are([YarnGlobals.TokenType.BeginCommand,YarnGlobals.TokenType.EndIf]):
|
||||||
|
elStatements.append(Statement.new(self,parser))
|
||||||
|
|
||||||
|
clauseElse.statements = elStatements
|
||||||
|
clauses.append(clauseElse)
|
||||||
|
|
||||||
|
#ignore dedent
|
||||||
|
while parser.next_symbol_is([YarnGlobals.TokenType.Dedent]):
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.Dedent])
|
||||||
|
|
||||||
|
|
||||||
|
#finish
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.BeginCommand])
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.EndIf])
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.EndCommand])
|
||||||
|
|
||||||
|
|
||||||
|
func tree_string(indentLevel : int)->String:
|
||||||
|
var info : PoolStringArray = []
|
||||||
|
var first : bool = true
|
||||||
|
|
||||||
|
for clause in clauses:
|
||||||
|
if first:
|
||||||
|
info.append(tab(indentLevel,"if:"))
|
||||||
|
elif clause.expression!=null:
|
||||||
|
info.append(tab(indentLevel,"Else If"))
|
||||||
|
else:
|
||||||
|
info.append(tab(indentLevel,"Else:"))
|
||||||
|
|
||||||
|
info.append(clause.tree_string(indentLevel +1))
|
||||||
|
|
||||||
|
return info.join("")
|
||||||
|
|
||||||
|
static func can_parse(parser)->bool:
|
||||||
|
return parser.next_symbols_are([YarnGlobals.TokenType.BeginCommand,YarnGlobals.TokenType.IfToken])
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ValueNode extends ParseNode:
|
||||||
|
const Value = preload("res://addons/Wol/core/value.gd")
|
||||||
|
const Lexer = preload("res://addons/Wol/core/compiler/lexer.gd")
|
||||||
|
var value : Value
|
||||||
|
|
||||||
|
func _init(parent:ParseNode, parser, token: Lexer.Token = null).(parent,parser):
|
||||||
|
|
||||||
|
var t : Lexer.Token = token
|
||||||
|
if t == null :
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.Number,
|
||||||
|
YarnGlobals.TokenType.Variable,YarnGlobals.TokenType.Str])
|
||||||
|
use_token(t)
|
||||||
|
|
||||||
|
#store value depending on type
|
||||||
|
func use_token(t:Lexer.Token):
|
||||||
|
match t.type:
|
||||||
|
YarnGlobals.TokenType.Number:
|
||||||
|
value = Value.new(float(t.value))
|
||||||
|
YarnGlobals.TokenType.Str:
|
||||||
|
value = Value.new(t.value)
|
||||||
|
YarnGlobals.TokenType.FalseToken:
|
||||||
|
value = Value.new(false)
|
||||||
|
YarnGlobals.TokenType.TrueToken:
|
||||||
|
value = Value.new(true)
|
||||||
|
YarnGlobals.TokenType.Variable:
|
||||||
|
value = Value.new(null)
|
||||||
|
value.type = YarnGlobals.ValueType.Variable
|
||||||
|
value.variable = t.value
|
||||||
|
YarnGlobals.TokenType.NullToken:
|
||||||
|
value = Value.new(null)
|
||||||
|
_:
|
||||||
|
printerr("%s, Invalid token type" % t.name)
|
||||||
|
|
||||||
|
func tree_string(indentLevel : int)->String:
|
||||||
|
return tab(indentLevel, "%s"%value.value())
|
||||||
|
|
||||||
|
|
||||||
|
#Expressions encompass a wide range of things like:
|
||||||
|
# math (1 + 2 - 5 * 3 / 10 % 2)
|
||||||
|
# Identifiers
|
||||||
|
# Values
|
||||||
|
class ExpressionNode extends ParseNode:
|
||||||
|
|
||||||
|
var type
|
||||||
|
var value : ValueNode
|
||||||
|
var function : String
|
||||||
|
var params : Array = []#ExpressionNode
|
||||||
|
|
||||||
|
func _init(parent:ParseNode,parser,value:ValueNode,function:String="",params:Array=[]).(parent,parser):
|
||||||
|
|
||||||
|
#no function - means value
|
||||||
|
if value!=null:
|
||||||
|
self.type = YarnGlobals.ExpressionType.Value
|
||||||
|
self.value = value
|
||||||
|
else:#function
|
||||||
|
|
||||||
|
self.type = YarnGlobals.ExpressionType.FunctionCall
|
||||||
|
self.function = function
|
||||||
|
self.params = params
|
||||||
|
|
||||||
|
func tree_string(indentLevel : int)->String:
|
||||||
|
var info : PoolStringArray = []
|
||||||
|
match type:
|
||||||
|
YarnGlobals.ExpressionType.Value:
|
||||||
|
return value.tree_string(indentLevel)
|
||||||
|
YarnGlobals.ExpressionType.FunctionCall:
|
||||||
|
info.append(tab(indentLevel,"Func[%s - params(%s)]:{"%[function,params.size()]))
|
||||||
|
for param in params:
|
||||||
|
#print("----> %s paramSize:%s"%[(function) , params.size()])
|
||||||
|
info.append(param.tree_string(indentLevel+1))
|
||||||
|
info.append(tab(indentLevel,"}"))
|
||||||
|
|
||||||
|
return info.join("")
|
||||||
|
|
||||||
|
#using Djikstra's shunting-yard algorithm to convert
|
||||||
|
#stream of expresions into postfix notaion, then
|
||||||
|
#build a tree of expressions
|
||||||
|
static func parse(parent:ParseNode,parser)->ExpressionNode:
|
||||||
|
|
||||||
|
|
||||||
|
var rpn : Array = []#token
|
||||||
|
var opStack : Array = []#token
|
||||||
|
|
||||||
|
#track params
|
||||||
|
var funcStack : Array = []#token
|
||||||
|
|
||||||
|
var validTypes : Array = [
|
||||||
|
YarnGlobals.TokenType.Number,
|
||||||
|
YarnGlobals.TokenType.Variable,
|
||||||
|
YarnGlobals.TokenType.Str,
|
||||||
|
YarnGlobals.TokenType.LeftParen,
|
||||||
|
YarnGlobals.TokenType.RightParen,
|
||||||
|
YarnGlobals.TokenType.Identifier,
|
||||||
|
YarnGlobals.TokenType.Comma,
|
||||||
|
YarnGlobals.TokenType.TrueToken,
|
||||||
|
YarnGlobals.TokenType.FalseToken,
|
||||||
|
YarnGlobals.TokenType.NullToken
|
||||||
|
]
|
||||||
|
validTypes+=Operator.op_types()
|
||||||
|
validTypes.invert()
|
||||||
|
|
||||||
|
var last #Token
|
||||||
|
|
||||||
|
#read expression content
|
||||||
|
while parser.tokens().size() > 0 && parser.next_symbol_is(validTypes):
|
||||||
|
var next = parser.expect_symbol(validTypes) #lexer.Token
|
||||||
|
|
||||||
|
if( next.type == YarnGlobals.TokenType.Variable ||
|
||||||
|
next.type == YarnGlobals.TokenType.Number ||
|
||||||
|
next.type == YarnGlobals.TokenType.Str ||
|
||||||
|
next.type == YarnGlobals.TokenType.TrueToken ||
|
||||||
|
next.type == YarnGlobals.TokenType.FalseToken ||
|
||||||
|
next.type == YarnGlobals.TokenType.NullToken ):
|
||||||
|
|
||||||
|
#output primitives
|
||||||
|
rpn.append(next)
|
||||||
|
elif next.type == YarnGlobals.TokenType.Identifier:
|
||||||
|
opStack.push_back(next)
|
||||||
|
funcStack.push_back(next)
|
||||||
|
|
||||||
|
#next token is parent - left
|
||||||
|
next = parser.expect_symbol([YarnGlobals.TokenType.LeftParen])
|
||||||
|
opStack.push_back(next)
|
||||||
|
elif next.type == YarnGlobals.TokenType.Comma:
|
||||||
|
|
||||||
|
#resolve sub expression before moving on
|
||||||
|
while opStack.back().type != YarnGlobals.TokenType.LeftParen:
|
||||||
|
var p = opStack.pop_back()
|
||||||
|
if p == null:
|
||||||
|
printerr("unbalanced parenthesis %s " % next.name)
|
||||||
|
break
|
||||||
|
rpn.append(p)
|
||||||
|
|
||||||
|
|
||||||
|
#next token in opStack left paren
|
||||||
|
# next parser token not allowed to be right paren or comma
|
||||||
|
if parser.next_symbol_is([YarnGlobals.TokenType.RightParen,
|
||||||
|
YarnGlobals.TokenType.Comma]):
|
||||||
|
printerr("Expected Expression : %s" % parser.tokens().front().name)
|
||||||
|
|
||||||
|
#find the closest function on stack
|
||||||
|
#increment parameters
|
||||||
|
funcStack.back().paramCount+=1
|
||||||
|
|
||||||
|
elif Operator.is_op(next.type):
|
||||||
|
#this is an operator
|
||||||
|
|
||||||
|
#if this is a minus, we need to determine if it is a
|
||||||
|
#unary minus or a binary minus.
|
||||||
|
#unary minus looks like this : -1
|
||||||
|
#binary minus looks like this 2 - 3
|
||||||
|
#thins get complex when we say stuff like: 1 + -1
|
||||||
|
#but its easier when we realize that a minus
|
||||||
|
#is only unary when the last token was a left paren,
|
||||||
|
#an operator, or its the first token.
|
||||||
|
|
||||||
|
if (next.type == YarnGlobals.TokenType.Minus):
|
||||||
|
if (last == null ||
|
||||||
|
last.type == YarnGlobals.TokenType.LeftParen ||
|
||||||
|
Operator.is_op(last.type)):
|
||||||
|
#unary minus
|
||||||
|
next.type = YarnGlobals.TokenType.UnaryMinus
|
||||||
|
|
||||||
|
#cannot assign inside expression
|
||||||
|
# x = a is the same as x == a
|
||||||
|
if next.type == YarnGlobals.TokenType.EqualToOrAssign:
|
||||||
|
next.type = YarnGlobals.TokenType.EqualTo
|
||||||
|
|
||||||
|
|
||||||
|
#operator precedence
|
||||||
|
while (ExpressionNode.is_apply_precedence(next.type,opStack)):
|
||||||
|
var op = opStack.pop_back()
|
||||||
|
rpn.append(op)
|
||||||
|
|
||||||
|
opStack.push_back(next)
|
||||||
|
|
||||||
|
elif next.type == YarnGlobals.TokenType.LeftParen:
|
||||||
|
#entered parenthesis sub expression
|
||||||
|
opStack.push_back(next)
|
||||||
|
elif next.type == YarnGlobals.TokenType.RightParen:
|
||||||
|
#leaving sub expression
|
||||||
|
# resolve order of operations
|
||||||
|
while opStack.back().type != YarnGlobals.TokenType.LeftParen:
|
||||||
|
rpn.append(opStack.pop_back())
|
||||||
|
if opStack.back() == null:
|
||||||
|
printerr("Unbalanced parenthasis #RightParen. Parser.ExpressionNode")
|
||||||
|
|
||||||
|
|
||||||
|
opStack.pop_back()
|
||||||
|
if opStack.back().type == YarnGlobals.TokenType.Identifier:
|
||||||
|
#function call
|
||||||
|
#last token == left paren this == no params
|
||||||
|
#else
|
||||||
|
#we have more than 1 param
|
||||||
|
if last.type != YarnGlobals.TokenType.LeftParen:
|
||||||
|
funcStack.back().paramCount+=1
|
||||||
|
|
||||||
|
rpn.append(opStack.pop_back())
|
||||||
|
funcStack.pop_back()
|
||||||
|
|
||||||
|
#record last token used
|
||||||
|
last = next
|
||||||
|
|
||||||
|
#no more tokens : pop operators to output
|
||||||
|
while opStack.size() > 0:
|
||||||
|
rpn.append(opStack.pop_back())
|
||||||
|
|
||||||
|
#if rpn is empty then this is not expression
|
||||||
|
if rpn.size() == 0:
|
||||||
|
printerr("Error parsing expression: Expression not found!")
|
||||||
|
|
||||||
|
#build expression tree
|
||||||
|
var first = rpn.front()
|
||||||
|
var evalStack : Array = []#ExpressionNode
|
||||||
|
|
||||||
|
while rpn.size() > 0:
|
||||||
|
|
||||||
|
var next = rpn.pop_front()
|
||||||
|
if Operator.is_op(next.type):
|
||||||
|
#operation
|
||||||
|
var info : OperatorInfo = Operator.op_info(next.type)
|
||||||
|
|
||||||
|
if evalStack.size() < info.arguments:
|
||||||
|
printerr("Error parsing : Not enough arguments for %s [ got %s expected - was %s]"%[YarnGlobals.token_type_name(next.type),evalStack.size(),info.arguments])
|
||||||
|
|
||||||
|
var params : Array = []#ExpressionNode
|
||||||
|
for i in range(info.arguments):
|
||||||
|
params.append(evalStack.pop_back())
|
||||||
|
|
||||||
|
params.invert()
|
||||||
|
|
||||||
|
var function : String = get_func_name(next.type)
|
||||||
|
|
||||||
|
var expression : ExpressionNode = ExpressionNode.new(parent,parser,null,function,params)
|
||||||
|
|
||||||
|
evalStack.append(expression)
|
||||||
|
|
||||||
|
elif next.type == YarnGlobals.TokenType.Identifier:
|
||||||
|
#function call
|
||||||
|
|
||||||
|
var function : String = next.value
|
||||||
|
|
||||||
|
var params : Array = []#ExpressionNode
|
||||||
|
for i in range(next.paramCount):
|
||||||
|
|
||||||
|
params.append(evalStack.pop_back())
|
||||||
|
|
||||||
|
params.invert()
|
||||||
|
|
||||||
|
var expression : ExpressionNode = ExpressionNode.new(parent,parser,null,function,params)
|
||||||
|
|
||||||
|
evalStack.append(expression)
|
||||||
|
else: #raw value
|
||||||
|
var value : ValueNode = ValueNode.new(parent,parser,next)
|
||||||
|
var expression : ExpressionNode = ExpressionNode.new(parent,parser,value)
|
||||||
|
evalStack.append(expression)
|
||||||
|
|
||||||
|
|
||||||
|
#we should have a single root expression left
|
||||||
|
#if more then we failed ---- NANI
|
||||||
|
if evalStack.size() != 1:
|
||||||
|
printerr("[%s] Error parsing expression (stack did not reduce correctly )"%first.name)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return evalStack.pop_back()
|
||||||
|
|
||||||
|
# static func can_parse(parser)->bool:
|
||||||
|
# return false
|
||||||
|
|
||||||
|
static func get_func_name(type)->String:
|
||||||
|
var string : String = ""
|
||||||
|
|
||||||
|
for key in YarnGlobals.TokenType.keys():
|
||||||
|
if YarnGlobals.TokenType[key] == type:
|
||||||
|
return key
|
||||||
|
return string
|
||||||
|
|
||||||
|
static func is_apply_precedence(type,operatorStack:Array)->bool:
|
||||||
|
if operatorStack.size() == 0:
|
||||||
|
return false
|
||||||
|
|
||||||
|
if !Operator.is_op(type):
|
||||||
|
printerr("Unable to parse expression!")
|
||||||
|
|
||||||
|
var second = operatorStack.back().type
|
||||||
|
|
||||||
|
if !Operator.is_op(second):
|
||||||
|
return false
|
||||||
|
|
||||||
|
var firstInfo : OperatorInfo = Operator.op_info(type)
|
||||||
|
var secondInfo : OperatorInfo = Operator.op_info(second)
|
||||||
|
|
||||||
|
if (firstInfo.associativity == Associativity.Left &&
|
||||||
|
firstInfo.precedence <= secondInfo.precedence):
|
||||||
|
return true
|
||||||
|
|
||||||
|
if (firstInfo.associativity == Associativity.Right &&
|
||||||
|
firstInfo.precedence < secondInfo.precedence):
|
||||||
|
return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
|
||||||
|
class Assignment extends ParseNode:
|
||||||
|
|
||||||
|
var destination : String
|
||||||
|
var value : ExpressionNode
|
||||||
|
var operation
|
||||||
|
|
||||||
|
func _init(parent:ParseNode,parser).(parent,parser):
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.BeginCommand])
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.Set])
|
||||||
|
destination = parser.expect_symbol([YarnGlobals.TokenType.Variable]).value
|
||||||
|
operation = parser.expect_symbol(Assignment.valid_ops()).type
|
||||||
|
value = ExpressionNode.parse(self,parser)
|
||||||
|
parser.expect_symbol([YarnGlobals.TokenType.EndCommand])
|
||||||
|
|
||||||
|
func tree_string(indentLevel : int)->String:
|
||||||
|
var info : PoolStringArray = []
|
||||||
|
info.append(tab(indentLevel,"set:"))
|
||||||
|
info.append(tab(indentLevel+1,destination))
|
||||||
|
info.append(tab(indentLevel+1,YarnGlobals.token_type_name(operation)))
|
||||||
|
info.append(value.tree_string(indentLevel+1))
|
||||||
|
return info.join("")
|
||||||
|
|
||||||
|
|
||||||
|
static func can_parse(parser)->bool:
|
||||||
|
return parser.next_symbols_are([
|
||||||
|
YarnGlobals.TokenType.BeginCommand,
|
||||||
|
YarnGlobals.TokenType.Set
|
||||||
|
])
|
||||||
|
|
||||||
|
static func valid_ops()->Array:
|
||||||
|
return [
|
||||||
|
YarnGlobals.TokenType.EqualToOrAssign,
|
||||||
|
YarnGlobals.TokenType.AddAssign,
|
||||||
|
YarnGlobals.TokenType.MinusAssign,
|
||||||
|
YarnGlobals.TokenType.DivideAssign,
|
||||||
|
YarnGlobals.TokenType.MultiplyAssign
|
||||||
|
]
|
||||||
|
|
||||||
|
class Operator extends ParseNode:
|
||||||
|
|
||||||
|
var opType
|
||||||
|
|
||||||
|
func _init(parent:ParseNode,parser,opType=null).(parent,parser):
|
||||||
|
|
||||||
|
if opType == null :
|
||||||
|
self.opType = parser.expect_symbol(Operator.op_types()).type
|
||||||
|
else:
|
||||||
|
self.opType = opType
|
||||||
|
|
||||||
|
func tree_string(indentLevel : int)->String:
|
||||||
|
var info : PoolStringArray = []
|
||||||
|
info.append(tab(indentLevel,opType))
|
||||||
|
return info.join("")
|
||||||
|
|
||||||
|
static func op_info(op)->OperatorInfo:
|
||||||
|
if !Operator.is_op(op) :
|
||||||
|
printerr("%s is not a valid operator" % op.name)
|
||||||
|
|
||||||
|
#determine associativity and operands
|
||||||
|
# each operand has
|
||||||
|
var TokenType = YarnGlobals.TokenType
|
||||||
|
|
||||||
|
match op:
|
||||||
|
TokenType.Not, TokenType.UnaryMinus:
|
||||||
|
return OperatorInfo.new(Associativity.Right,30,1)
|
||||||
|
TokenType.Multiply,TokenType.Divide,TokenType.Modulo:
|
||||||
|
return OperatorInfo.new(Associativity.Left,20,2)
|
||||||
|
TokenType.Add,TokenType.Minus:
|
||||||
|
return OperatorInfo.new(Associativity.Left,15,2)
|
||||||
|
TokenType.GreaterThan,TokenType.LessThan,TokenType.GreaterThanOrEqualTo,TokenType.LessThanOrEqualTo:
|
||||||
|
return OperatorInfo.new(Associativity.Left,10,2)
|
||||||
|
TokenType.EqualTo,TokenType.EqualToOrAssign,TokenType.NotEqualTo:
|
||||||
|
return OperatorInfo.new(Associativity.Left,5,2)
|
||||||
|
TokenType.And:
|
||||||
|
return OperatorInfo.new(Associativity.Left,4,2)
|
||||||
|
TokenType.Or:
|
||||||
|
return OperatorInfo.new(Associativity.Left,3,2)
|
||||||
|
TokenType.Xor:
|
||||||
|
return OperatorInfo.new(Associativity.Left,2,2)
|
||||||
|
_:
|
||||||
|
printerr("Unknown operator: %s" % op.name)
|
||||||
|
return null
|
||||||
|
|
||||||
|
static func is_op(type)->bool:
|
||||||
|
return type in op_types()
|
||||||
|
|
||||||
|
static func op_types()->Array:
|
||||||
|
return [
|
||||||
|
YarnGlobals.TokenType.Not,
|
||||||
|
YarnGlobals.TokenType.UnaryMinus,
|
||||||
|
|
||||||
|
YarnGlobals.TokenType.Add,
|
||||||
|
YarnGlobals.TokenType.Minus,
|
||||||
|
YarnGlobals.TokenType.Divide,
|
||||||
|
YarnGlobals.TokenType.Multiply,
|
||||||
|
YarnGlobals.TokenType.Modulo,
|
||||||
|
|
||||||
|
YarnGlobals.TokenType.EqualToOrAssign,
|
||||||
|
YarnGlobals.TokenType.EqualTo,
|
||||||
|
YarnGlobals.TokenType.GreaterThan,
|
||||||
|
YarnGlobals.TokenType.GreaterThanOrEqualTo,
|
||||||
|
YarnGlobals.TokenType.LessThan,
|
||||||
|
YarnGlobals.TokenType.LessThanOrEqualTo,
|
||||||
|
YarnGlobals.TokenType.NotEqualTo,
|
||||||
|
|
||||||
|
YarnGlobals.TokenType.And,
|
||||||
|
YarnGlobals.TokenType.Or,
|
||||||
|
|
||||||
|
YarnGlobals.TokenType.Xor
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class OperatorInfo:
|
||||||
|
var associativity
|
||||||
|
var precedence : int
|
||||||
|
var arguments : int
|
||||||
|
|
||||||
|
func _init(associativity,precedence:int,arguments:int):
|
||||||
|
self.associativity = associativity
|
||||||
|
self.precedence = precedence
|
||||||
|
self.arguments = arguments
|
||||||
|
|
||||||
|
|
||||||
|
class Clause:
|
||||||
|
var expression : ExpressionNode
|
||||||
|
var statements : Array = [] #Statement
|
||||||
|
|
||||||
|
func _init(expression:ExpressionNode = null, statements : Array = []):
|
||||||
|
self.expression = expression
|
||||||
|
self.statements = statements
|
||||||
|
|
||||||
|
func tree_string(indentLevel : int)->String:
|
||||||
|
var info : PoolStringArray = []
|
||||||
|
if expression!=null:
|
||||||
|
info.append(expression.tree_string(indentLevel))
|
||||||
|
info.append(tab(indentLevel,"{"))
|
||||||
|
for statement in statements:
|
||||||
|
info.append(statement.tree_string(indentLevel+1))
|
||||||
|
|
||||||
|
info.append(tab(indentLevel,"}"))
|
||||||
|
return info.join("")
|
||||||
|
|
||||||
|
func tab(indentLevel : int , input : String,newLine : bool = true)->String:
|
||||||
|
return ("%*s| %s%s"% [indentLevel*2,"",input,("" if !newLine else "\n")])
|
136
addons/Wol/core/dialogue.gd
Normal file
136
addons/Wol/core/dialogue.gd
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
const DEFAULT_START = "Start"
|
||||||
|
const FMF_PLACEHOLDE = "<VALUE PLACEHOLDER>"
|
||||||
|
|
||||||
|
const StandardLibrary = preload("res://addons/Wol/core/libraries/standard.gd")
|
||||||
|
const VirtualMachine = preload("res://addons/Wol/core/virtual_machine.gd")
|
||||||
|
const YarnLibrary = preload("res://addons/Wol/core/library.gd")
|
||||||
|
const Value = preload("res://addons/Wol/core/value.gd")
|
||||||
|
const YarnProgram = preload("res://addons/Wol/core/program/program.gd")
|
||||||
|
|
||||||
|
var _variableStorage
|
||||||
|
|
||||||
|
var _debugLog
|
||||||
|
var _errLog
|
||||||
|
|
||||||
|
var _program
|
||||||
|
var library
|
||||||
|
|
||||||
|
var _vm : VirtualMachine
|
||||||
|
|
||||||
|
var _visitedNodeCount : Dictionary = {}
|
||||||
|
|
||||||
|
var executionComplete : bool
|
||||||
|
|
||||||
|
func _init(variableStorage):
|
||||||
|
_variableStorage = variableStorage
|
||||||
|
_vm = VirtualMachine.new(self)
|
||||||
|
library = YarnLibrary.new()
|
||||||
|
_debugLog = funcref(self, "dlog")
|
||||||
|
_errLog = funcref(self, "elog")
|
||||||
|
executionComplete = false
|
||||||
|
|
||||||
|
# import the standard library
|
||||||
|
# this contains math constants, operations and checks
|
||||||
|
library.import_library(StandardLibrary.new())#FIX
|
||||||
|
|
||||||
|
#add a function to lib that checks if node is visited
|
||||||
|
library.register_function("visited", -1, funcref(self, "is_node_visited"), true)
|
||||||
|
|
||||||
|
#add function to lib that gets the node visit count
|
||||||
|
library.register_function("visit_count", -1, funcref(self, "node_visit_count"), true)
|
||||||
|
|
||||||
|
|
||||||
|
func dlog(message:String):
|
||||||
|
print("YARN_DEBUG : %s" % message)
|
||||||
|
|
||||||
|
func elog(message:String):
|
||||||
|
print("YARN_ERROR : %s" % message)
|
||||||
|
|
||||||
|
func is_active():
|
||||||
|
return get_exec_state() != YarnGlobals.ExecutionState.Stopped
|
||||||
|
|
||||||
|
#gets the current execution state of the virtual machine
|
||||||
|
func get_exec_state():
|
||||||
|
return _vm.executionState
|
||||||
|
|
||||||
|
func set_selected_option(option:int):
|
||||||
|
_vm.set_selected_option(option)
|
||||||
|
|
||||||
|
func set_node(name:String = DEFAULT_START):
|
||||||
|
_vm.set_node(name)
|
||||||
|
|
||||||
|
func resume():
|
||||||
|
if _vm.executionState == YarnGlobals.ExecutionState.Running:
|
||||||
|
print('BLOCKED')
|
||||||
|
return
|
||||||
|
_vm.resume()
|
||||||
|
|
||||||
|
func pause():
|
||||||
|
_vm.pause()
|
||||||
|
|
||||||
|
func stop():
|
||||||
|
_vm.stop()
|
||||||
|
|
||||||
|
func get_all_nodes():
|
||||||
|
return _program.yarnNodes.keys()
|
||||||
|
|
||||||
|
func current_node():
|
||||||
|
return _vm.get_current()
|
||||||
|
|
||||||
|
func get_node_id(name):
|
||||||
|
if _program.nodes.size() == 0:
|
||||||
|
_errLog.call_func("No nodes loaded")
|
||||||
|
return ""
|
||||||
|
if _program.nodes.has(name):
|
||||||
|
return "id:"+name
|
||||||
|
else:
|
||||||
|
_errLog.call_func("No node named [%s] exists" % name)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
func unloadAll(clear_visited:bool = true):
|
||||||
|
if clear_visited :
|
||||||
|
_visitedNodeCount.clear()
|
||||||
|
_program = null
|
||||||
|
|
||||||
|
func dump()->String:
|
||||||
|
return _program.dump(library)
|
||||||
|
|
||||||
|
func node_exists(name:String)->bool:
|
||||||
|
return _program.nodes.has(name)
|
||||||
|
|
||||||
|
func set_program(program):
|
||||||
|
_program = program
|
||||||
|
_vm.set_program(_program)
|
||||||
|
_vm.reset()
|
||||||
|
|
||||||
|
func get_program():
|
||||||
|
return _program
|
||||||
|
|
||||||
|
func get_vm():
|
||||||
|
return _vm
|
||||||
|
|
||||||
|
func is_node_visited(node = _vm.current_node_name()):
|
||||||
|
return node_visit_count(node) > 0
|
||||||
|
|
||||||
|
func node_visit_count(node = _vm.current_node_name()):
|
||||||
|
if node is Value:
|
||||||
|
node = _program.yarnStrings[node.value()].text
|
||||||
|
|
||||||
|
var visitCount : int = 0
|
||||||
|
if _visitedNodeCount.has(node):
|
||||||
|
visitCount = _visitedNodeCount[node]
|
||||||
|
|
||||||
|
|
||||||
|
print("visit count for %s is %d" % [node, visitCount])
|
||||||
|
|
||||||
|
return visitCount
|
||||||
|
|
||||||
|
func get_visited_nodes():
|
||||||
|
return _visitedNodeCount.keys()
|
||||||
|
|
||||||
|
func set_visited_nodes(visitedList):
|
||||||
|
_visitedNodeCount.clear()
|
||||||
|
for string in visitedList:
|
||||||
|
_visitedNodeCount[string] = 1
|
6
addons/Wol/core/dialogue/command.gd
Normal file
6
addons/Wol/core/dialogue/command.gd
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
extends Object
|
||||||
|
|
||||||
|
var command : String
|
||||||
|
|
||||||
|
func _init(command : String):
|
||||||
|
self.command = command
|
16
addons/Wol/core/dialogue/format_function.gd
Normal file
16
addons/Wol/core/dialogue/format_function.gd
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
|
||||||
|
# Declare member variables here. Examples:
|
||||||
|
# var a = 2
|
||||||
|
# var b = "text"
|
||||||
|
|
||||||
|
|
||||||
|
# Called when the node enters the scene tree for the first time.
|
||||||
|
func _ready():
|
||||||
|
pass # Replace with function body.
|
||||||
|
|
||||||
|
|
||||||
|
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||||
|
#func _process(delta):
|
||||||
|
# pass
|
11
addons/Wol/core/dialogue/line.gd
Normal file
11
addons/Wol/core/dialogue/line.gd
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
extends Object
|
||||||
|
|
||||||
|
const LineInfo = preload("res://addons/Wol/core/program/yarn_line.gd")
|
||||||
|
|
||||||
|
var id : String
|
||||||
|
var substitutions : Array = []#String
|
||||||
|
var info : LineInfo
|
||||||
|
|
||||||
|
func _init(id: String, info: LineInfo):
|
||||||
|
self.id = id
|
||||||
|
self.info = info
|
13
addons/Wol/core/dialogue/option.gd
Normal file
13
addons/Wol/core/dialogue/option.gd
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
extends Object
|
||||||
|
|
||||||
|
const Line = preload("res://addons/Wol/core/dialogue/line.gd")
|
||||||
|
|
||||||
|
var line : Line
|
||||||
|
var id : int
|
||||||
|
var destination : String
|
||||||
|
|
||||||
|
func _init(line : Line,id : int, destination: String):
|
||||||
|
self.line = line
|
||||||
|
self.id = id
|
||||||
|
self.destination = destination
|
||||||
|
|
39
addons/Wol/core/function_info.gd
Normal file
39
addons/Wol/core/function_info.gd
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
extends Object
|
||||||
|
var Value : GDScript = load("res://addons/Wol/core/value.gd")
|
||||||
|
|
||||||
|
#name of the function
|
||||||
|
var name : String
|
||||||
|
|
||||||
|
#param count of this function
|
||||||
|
# -1 means variable arguments
|
||||||
|
var paramCount : int = 0
|
||||||
|
#function implementation
|
||||||
|
var function : FuncRef
|
||||||
|
var returnsValue : bool = false
|
||||||
|
|
||||||
|
func _init(name: String, paramCount: int, function: FuncRef = null, returnsValue: bool = false):
|
||||||
|
self.name = name
|
||||||
|
self.paramCount = paramCount
|
||||||
|
self.function = function
|
||||||
|
self.returnsValue = returnsValue
|
||||||
|
|
||||||
|
func invoke(params = []):
|
||||||
|
var length = 0
|
||||||
|
if params != null:
|
||||||
|
length = params.size()
|
||||||
|
|
||||||
|
if check_param_count(length):
|
||||||
|
if returnsValue:
|
||||||
|
if length > 0:
|
||||||
|
return Value.new(function.call_funcv(params))
|
||||||
|
else:
|
||||||
|
return Value.new(function.call_func())
|
||||||
|
else:
|
||||||
|
if length > 0:
|
||||||
|
function.call_funcv(params)
|
||||||
|
else :
|
||||||
|
function.call_func()
|
||||||
|
return null
|
||||||
|
|
||||||
|
func check_param_count(paramCount: int):
|
||||||
|
return self.paramCount == paramCount || self.paramCount == -1
|
69
addons/Wol/core/libraries/standard.gd
Normal file
69
addons/Wol/core/libraries/standard.gd
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
extends "res://addons/Wol/core/library.gd"
|
||||||
|
|
||||||
|
const Value = preload("res://addons/Wol/core/value.gd")
|
||||||
|
|
||||||
|
func _init():
|
||||||
|
register_function("Add",2,funcref(self,"add"),true)
|
||||||
|
register_function("Minus",2,funcref(self,"sub"),true)
|
||||||
|
register_function("UnaryMinus",1,funcref(self,"unary_minus"),true)
|
||||||
|
register_function("Divide",2,funcref(self,"div"),true)
|
||||||
|
register_function("Multiply",2,funcref(self,"mul"),true)
|
||||||
|
register_function("Modulo",2,funcref(self,"mod"),true)
|
||||||
|
register_function("EqualTo",2,funcref(self,"equal"),true)
|
||||||
|
register_function("NotEqualTo",2,funcref(self,"noteq"),true)
|
||||||
|
register_function("GreaterThan",2,funcref(self,"ge"),true)
|
||||||
|
register_function("GreaterThanOrEqualTo",2,funcref(self,"geq"),true)
|
||||||
|
register_function("LessThan",2,funcref(self,"le"),true)
|
||||||
|
register_function("LessThanOrEqualTo",2,funcref(self,"leq"),true)
|
||||||
|
register_function("And",2,funcref(self,"land"),true)
|
||||||
|
register_function("Or",2,funcref(self,"lor"),true)
|
||||||
|
register_function("Xor",2,funcref(self,"xor"),true)
|
||||||
|
register_function("Not",1,funcref(self,"lnot"),true)
|
||||||
|
|
||||||
|
func add(param1:Value,param2:Value):
|
||||||
|
return param1.add(param2)
|
||||||
|
|
||||||
|
func sub(param1:Value,param2:Value):
|
||||||
|
return param1.sub(param2)
|
||||||
|
|
||||||
|
func unary_minus(param1:Value):
|
||||||
|
return param1.negative()
|
||||||
|
|
||||||
|
func div(param1:Value,param2:Value):
|
||||||
|
return param1.div(param2)
|
||||||
|
|
||||||
|
func mul(param1:Value,param2:Value):
|
||||||
|
return param1.mult(param2)
|
||||||
|
|
||||||
|
func mod(param1:Value,param2:Value):
|
||||||
|
return param1.mod(param2)
|
||||||
|
|
||||||
|
func equal(param1:Value,param2:Value):
|
||||||
|
return param1.equals(param2)
|
||||||
|
|
||||||
|
func noteq(param1:Value,param2:Value):
|
||||||
|
return !param1.equals(param2)
|
||||||
|
|
||||||
|
func ge(param1:Value,param2:Value):
|
||||||
|
return param1.greater(param2)
|
||||||
|
|
||||||
|
func geq(param1:Value,param2:Value):
|
||||||
|
return param1.geq(param2)
|
||||||
|
|
||||||
|
func le(param1:Value,param2:Value):
|
||||||
|
return param1.less(param2)
|
||||||
|
|
||||||
|
func leq(param1:Value,param2:Value):
|
||||||
|
return param1.leq(param2)
|
||||||
|
|
||||||
|
func land(param1:Value,param2:Value):
|
||||||
|
return param1.as_bool() && param2.as_bool()
|
||||||
|
|
||||||
|
func lor(param1:Value,param2:Value):
|
||||||
|
return param1.as_bool() || param2.as_bool()
|
||||||
|
|
||||||
|
func xor(param1:Value,param2:Value):
|
||||||
|
return param1.as_bool() != param2.as_bool()
|
||||||
|
|
||||||
|
func lnot(param1:Value):
|
||||||
|
return !param1.as_bool()
|
27
addons/Wol/core/library.gd
Normal file
27
addons/Wol/core/library.gd
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
extends Object
|
||||||
|
|
||||||
|
const FunctionInfo = preload("res://addons/Wol/core/function_info.gd")
|
||||||
|
|
||||||
|
var functions : Dictionary = {}# String , FunctionInfo
|
||||||
|
|
||||||
|
func get_function(name:String)->FunctionInfo:
|
||||||
|
if functions.has(name):
|
||||||
|
return functions[name]
|
||||||
|
else :
|
||||||
|
printerr("Invalid Function: %s"% name)
|
||||||
|
return null
|
||||||
|
|
||||||
|
func import_library(other)->void:
|
||||||
|
YarnGlobals.merge_dir(functions,other.functions)
|
||||||
|
|
||||||
|
func register_function(name: String, paramCount: int, function: FuncRef, returnsValue: bool):
|
||||||
|
var functionInfo: FunctionInfo = FunctionInfo.new(name, paramCount, function, returnsValue)
|
||||||
|
functions[name] = functionInfo
|
||||||
|
|
||||||
|
func deregister_function(name: String):
|
||||||
|
if !functions.erase(name):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
17
addons/Wol/core/program/instruction.gd
Normal file
17
addons/Wol/core/program/instruction.gd
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
extends Object
|
||||||
|
|
||||||
|
const Operand = preload("res://addons/Wol/core/program/operand.gd")
|
||||||
|
|
||||||
|
var operation : int #bytcode
|
||||||
|
var operands : Array #Operands
|
||||||
|
|
||||||
|
func _init(other=null):
|
||||||
|
if other != null && other.get_script() == self.get_script():
|
||||||
|
self.operation = other.operation
|
||||||
|
self.operands += other.operands
|
||||||
|
|
||||||
|
func dump(program,library)->String:
|
||||||
|
return "InstructionInformation:NotImplemented"
|
||||||
|
|
||||||
|
func _to_string():
|
||||||
|
return YarnGlobals.bytecode_name(operation) + ':' + operands as String
|
58
addons/Wol/core/program/operand.gd
Normal file
58
addons/Wol/core/program/operand.gd
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
extends Object
|
||||||
|
|
||||||
|
enum ValueType{
|
||||||
|
None,
|
||||||
|
StringValue,
|
||||||
|
BooleanValue,
|
||||||
|
FloatValue
|
||||||
|
}
|
||||||
|
|
||||||
|
var value
|
||||||
|
|
||||||
|
var type
|
||||||
|
|
||||||
|
func _init(value):
|
||||||
|
if typeof(value) == TYPE_OBJECT && value.get_script() == self.get_script():
|
||||||
|
#operand
|
||||||
|
self.set_value(value.value)
|
||||||
|
else:
|
||||||
|
set_value(value)
|
||||||
|
|
||||||
|
func set_value(value):
|
||||||
|
match typeof(value):
|
||||||
|
TYPE_REAL,TYPE_INT:
|
||||||
|
set_number(value)
|
||||||
|
TYPE_BOOL:
|
||||||
|
set_boolean(value)
|
||||||
|
TYPE_STRING:
|
||||||
|
set_string(value)
|
||||||
|
_:
|
||||||
|
pass
|
||||||
|
|
||||||
|
func set_boolean(value: bool):
|
||||||
|
_value(value)
|
||||||
|
type = ValueType.BooleanValue
|
||||||
|
return self
|
||||||
|
|
||||||
|
func set_string(value:String):
|
||||||
|
_value(value)
|
||||||
|
type = ValueType.StringValue
|
||||||
|
return self
|
||||||
|
|
||||||
|
func set_number(value:float):
|
||||||
|
_value(value)
|
||||||
|
type = ValueType.FloatValue
|
||||||
|
return self
|
||||||
|
|
||||||
|
func clear_value():
|
||||||
|
type = ValueType.None
|
||||||
|
value = null
|
||||||
|
|
||||||
|
func clone():
|
||||||
|
return get_script().new(self)
|
||||||
|
|
||||||
|
func _to_string():
|
||||||
|
return "Operand[%s:%s]" % [type,value]
|
||||||
|
|
||||||
|
func _value(value):
|
||||||
|
self.value = value
|
30
addons/Wol/core/program/program.gd
Normal file
30
addons/Wol/core/program/program.gd
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
var programName : String
|
||||||
|
var yarnStrings : Dictionary = {}
|
||||||
|
var yarnNodes : Dictionary = {}
|
||||||
|
|
||||||
|
func get_node_tags(name:String)->Array:
|
||||||
|
return yarnNodes[name].tags
|
||||||
|
|
||||||
|
func get_yarn_string(key:String)->String:
|
||||||
|
return yarnStrings[key]
|
||||||
|
|
||||||
|
func get_node_text(name:String)->String:
|
||||||
|
var key = yarnNodes[name].sourceId
|
||||||
|
return get_yarn_string(key)
|
||||||
|
|
||||||
|
#possible support for line tags
|
||||||
|
func get_untagged_strings()->Dictionary:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
func merge(other):
|
||||||
|
pass
|
||||||
|
|
||||||
|
func include(other):
|
||||||
|
pass
|
||||||
|
|
||||||
|
func dump(library):
|
||||||
|
print("not yet implemented")
|
||||||
|
pass
|
||||||
|
|
16
addons/Wol/core/program/yarn_line.gd
Normal file
16
addons/Wol/core/program/yarn_line.gd
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
extends Object
|
||||||
|
|
||||||
|
var text : String
|
||||||
|
var nodeName : String
|
||||||
|
var lineNumber : int
|
||||||
|
var fileName : String
|
||||||
|
var implicit : bool
|
||||||
|
var meta : Array = []
|
||||||
|
|
||||||
|
func _init(text:String, nodeName:String, lineNumber:int, fileName:String, implicit:bool, meta:Array):
|
||||||
|
self.text = text
|
||||||
|
self.nodeName = nodeName
|
||||||
|
self.fileName = fileName
|
||||||
|
self.implicit = implicit
|
||||||
|
self.meta = meta
|
||||||
|
|
33
addons/Wol/core/program/yarn_node.gd
Normal file
33
addons/Wol/core/program/yarn_node.gd
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
extends Object
|
||||||
|
|
||||||
|
var nodeName : String
|
||||||
|
var instructions : Array = []
|
||||||
|
var labels : Dictionary
|
||||||
|
var tags: Array
|
||||||
|
var sourceId : String
|
||||||
|
|
||||||
|
func _init(other = null):
|
||||||
|
if other != null && other.get_script() == self.get_script():
|
||||||
|
nodeName = other.nodeName
|
||||||
|
instructions+=other.instructions
|
||||||
|
for key in other.labels.keys():
|
||||||
|
labels[key] = other.labels[key]
|
||||||
|
tags += other.tags
|
||||||
|
sourceId = other.sourceId
|
||||||
|
|
||||||
|
func equals(other)->bool:
|
||||||
|
|
||||||
|
if other.get_script() != self.get_script():
|
||||||
|
return false
|
||||||
|
if other.name != self.name:
|
||||||
|
return false
|
||||||
|
if other.instructions != self.instructions:
|
||||||
|
return false
|
||||||
|
if other.label != self.label:
|
||||||
|
return false
|
||||||
|
if other.sourceId != self.sourceId:
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
func _to_string():
|
||||||
|
return "Node[%s:%s]" % [nodeName,sourceId]
|
151
addons/Wol/core/value.gd
Normal file
151
addons/Wol/core/value.gd
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
extends Object
|
||||||
|
|
||||||
|
const YarnGlobals = preload("res://addons/Wol/autoloads/execution_states.gd")
|
||||||
|
|
||||||
|
const NULL_STRING : String = "null"
|
||||||
|
const FALSE_STRING : String= "false"
|
||||||
|
const TRUE_STRING : String = "true"
|
||||||
|
const NANI : String = "NaN"
|
||||||
|
|
||||||
|
var type : int = YarnGlobals.ValueType.Nullean
|
||||||
|
var number : float = 0
|
||||||
|
var string : String = ""
|
||||||
|
var variable : String = ""
|
||||||
|
var boolean : bool = false
|
||||||
|
|
||||||
|
|
||||||
|
func _init(value = NANI):
|
||||||
|
if typeof(value) == TYPE_OBJECT && value.get_script() == self.get_script():
|
||||||
|
if value.type == YarnGlobals.ValueType.Variable:
|
||||||
|
self.type = value.type
|
||||||
|
self.variable = value.variable
|
||||||
|
else:
|
||||||
|
set_value(value)
|
||||||
|
|
||||||
|
func value():
|
||||||
|
match type:
|
||||||
|
YarnGlobals.ValueType.Number:
|
||||||
|
return number
|
||||||
|
YarnGlobals.ValueType.Str:
|
||||||
|
return string
|
||||||
|
YarnGlobals.ValueType.Boolean:
|
||||||
|
return boolean
|
||||||
|
YarnGlobals.ValueType.Variable:
|
||||||
|
return variable
|
||||||
|
return null
|
||||||
|
|
||||||
|
func as_bool():
|
||||||
|
match type:
|
||||||
|
YarnGlobals.ValueType.Number:
|
||||||
|
return number != 0
|
||||||
|
YarnGlobals.ValueType.Str:
|
||||||
|
return !string.empty()
|
||||||
|
YarnGlobals.ValueType.Boolean:
|
||||||
|
return boolean
|
||||||
|
return false
|
||||||
|
|
||||||
|
func as_string():
|
||||||
|
return "%s" % value()
|
||||||
|
|
||||||
|
func as_number():
|
||||||
|
match type:
|
||||||
|
YarnGlobals.ValueType.Number:
|
||||||
|
return number
|
||||||
|
YarnGlobals.ValueType.Str:
|
||||||
|
return float(string)
|
||||||
|
YarnGlobals.ValueType.Boolean:
|
||||||
|
return 0.0 if !boolean else 1.0
|
||||||
|
return .0
|
||||||
|
|
||||||
|
func set_value(value):
|
||||||
|
if value == null || (typeof(value) == TYPE_STRING && value == NANI):
|
||||||
|
type = YarnGlobals.ValueType.Nullean
|
||||||
|
return
|
||||||
|
|
||||||
|
match typeof(value):
|
||||||
|
TYPE_INT,TYPE_REAL:
|
||||||
|
type = YarnGlobals.ValueType.Number
|
||||||
|
number = value
|
||||||
|
TYPE_STRING:
|
||||||
|
type = YarnGlobals.ValueType.Str
|
||||||
|
string = value
|
||||||
|
TYPE_BOOL:
|
||||||
|
type = YarnGlobals.ValueType.Boolean
|
||||||
|
boolean = value
|
||||||
|
|
||||||
|
#operations >>
|
||||||
|
|
||||||
|
#addition
|
||||||
|
func add(other):
|
||||||
|
if self.type == YarnGlobals.ValueType.Str || other.type == YarnGlobals.ValueType.Str:
|
||||||
|
return get_script().new("%s%s"%[self.value(),other.value()])
|
||||||
|
if self.type == YarnGlobals.ValueType.Number && other.type == YarnGlobals.ValueType.Number:
|
||||||
|
return get_script().new(self.number + other.number)
|
||||||
|
return null
|
||||||
|
|
||||||
|
func equals(other)->bool:
|
||||||
|
if other.get_script() != self.get_script():
|
||||||
|
return false
|
||||||
|
if other.value() != self.value():
|
||||||
|
return false
|
||||||
|
return true #refine this
|
||||||
|
|
||||||
|
#subtract
|
||||||
|
func sub(other):
|
||||||
|
if self.type == YarnGlobals.ValueType.Str || other.type == YarnGlobals.ValueType.Str:
|
||||||
|
return get_script().new(str(value()).replace(str(other.value()),""))
|
||||||
|
if self.type == YarnGlobals.ValueType.Number && other.type == YarnGlobals.ValueType.Number:
|
||||||
|
return get_script().new(self.number - other.number)
|
||||||
|
return null
|
||||||
|
|
||||||
|
#multiply
|
||||||
|
func mult(other):
|
||||||
|
if self.type == YarnGlobals.ValueType.Number && other.type == YarnGlobals.ValueType.Number:
|
||||||
|
return get_script().new(self.number * other.number)
|
||||||
|
return null
|
||||||
|
|
||||||
|
#division
|
||||||
|
func div(other):
|
||||||
|
if self.type == YarnGlobals.ValueType.Number && other.type == YarnGlobals.ValueType.Number:
|
||||||
|
return get_script().new(self.number / other.number)
|
||||||
|
return null
|
||||||
|
|
||||||
|
#modulus
|
||||||
|
func mod(other):
|
||||||
|
if self.type == YarnGlobals.ValueType.Number && other.type == YarnGlobals.ValueType.Number:
|
||||||
|
return get_script().new(self.number % other.number)
|
||||||
|
return null
|
||||||
|
|
||||||
|
func negative():
|
||||||
|
if self.type == YarnGlobals.ValueType.Number:
|
||||||
|
return get_script().new(-self.number)
|
||||||
|
return null
|
||||||
|
|
||||||
|
#greater than other
|
||||||
|
func greater(other)->bool:
|
||||||
|
if self.type == YarnGlobals.ValueType.Number && other.type == YarnGlobals.ValueType.Number:
|
||||||
|
return self.number > other.number
|
||||||
|
return false
|
||||||
|
|
||||||
|
#less than other
|
||||||
|
func less(other)->bool:
|
||||||
|
if self.type == YarnGlobals.ValueType.Number && other.type == YarnGlobals.ValueType.Number:
|
||||||
|
return self.number < other.number
|
||||||
|
return false
|
||||||
|
|
||||||
|
#greater than or equal to other
|
||||||
|
func geq(other)->bool:
|
||||||
|
if self.type == YarnGlobals.ValueType.Number && other.type == YarnGlobals.ValueType.Number:
|
||||||
|
return self.number > other.number || self.equals(other)
|
||||||
|
return false
|
||||||
|
|
||||||
|
#lesser than or equal to other
|
||||||
|
func leq(other)->bool:
|
||||||
|
if self.type == YarnGlobals.ValueType.Number && other.type == YarnGlobals.ValueType.Number:
|
||||||
|
return self.number < other.number || self.equals(other)
|
||||||
|
return false
|
||||||
|
|
||||||
|
func _to_string():
|
||||||
|
return "value(type[%s]: %s)" % [type,value()]
|
||||||
|
|
||||||
|
|
23
addons/Wol/core/variable_storage.gd
Normal file
23
addons/Wol/core/variable_storage.gd
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
signal values_changed
|
||||||
|
|
||||||
|
const Value = preload("res://addons/Wol/core/value.gd")
|
||||||
|
|
||||||
|
var variables = {}
|
||||||
|
|
||||||
|
func set_value(name, value):
|
||||||
|
print('SETTING VALUES %s: %s' % [name, value])
|
||||||
|
if !(value is Value):
|
||||||
|
variables[name] = Value.new(value)
|
||||||
|
else:
|
||||||
|
variables[name] = value
|
||||||
|
|
||||||
|
emit_signal('values_changed')
|
||||||
|
|
||||||
|
func get_value(name):
|
||||||
|
return variables.get(name)
|
||||||
|
|
||||||
|
func clear_values():
|
||||||
|
variables.clear()
|
||||||
|
emit_signal('values_changed')
|
369
addons/Wol/core/virtual_machine.gd
Normal file
369
addons/Wol/core/virtual_machine.gd
Normal file
|
@ -0,0 +1,369 @@
|
||||||
|
extends Node
|
||||||
|
var YarnGlobals = load("res://addons/Wol/autoloads/execution_states.gd")
|
||||||
|
|
||||||
|
var FunctionInfo = load("res://addons/Wol/core/function_info.gd")
|
||||||
|
var Value = load("res://addons/Wol/core/value.gd")
|
||||||
|
var YarnProgram = load("res://addons/Wol/core/program/program.gd")
|
||||||
|
var YarnNode = load("res://addons/Wol/core/program/yarn_node.gd")
|
||||||
|
var Instruction = load("res://addons/Wol/core/program/instruction.gd")
|
||||||
|
var Line = load("res://addons/Wol/core/dialogue/line.gd")
|
||||||
|
var Command = load("res://addons/Wol/core/dialogue/command.gd")
|
||||||
|
var Option = load("res://addons/Wol/core/dialogue/option.gd")
|
||||||
|
|
||||||
|
const EXECUTION_COMPLETE : String = "execution_complete_command"
|
||||||
|
|
||||||
|
var NULL_VALUE = Value.new(null)
|
||||||
|
|
||||||
|
# Function references to handlers
|
||||||
|
var lineHandler
|
||||||
|
var optionsHandler
|
||||||
|
var commandHandler
|
||||||
|
var nodeStartHandler
|
||||||
|
var nodeCompleteHandler
|
||||||
|
var dialogueCompleteHandler
|
||||||
|
|
||||||
|
var _dialogue
|
||||||
|
var _program
|
||||||
|
var _state
|
||||||
|
|
||||||
|
var _currentNode
|
||||||
|
|
||||||
|
var executionState = YarnGlobals.ExecutionState.Stopped
|
||||||
|
|
||||||
|
var string_table = {}
|
||||||
|
|
||||||
|
func _init(dialogue):
|
||||||
|
self._dialogue = dialogue
|
||||||
|
_state = VmState.new()
|
||||||
|
|
||||||
|
|
||||||
|
func set_program(program):
|
||||||
|
_program = program
|
||||||
|
|
||||||
|
#set the node to run
|
||||||
|
#return true if successeful false if no node
|
||||||
|
#of that name found
|
||||||
|
func set_node(name:String)->bool:
|
||||||
|
if _program == null || _program.yarnNodes.size() == 0:
|
||||||
|
printerr("Could not load %s : no nodes loaded" % name)
|
||||||
|
return false
|
||||||
|
|
||||||
|
if !_program.yarnNodes.has(name):
|
||||||
|
executionState = YarnGlobals.ExecutionState.Stopped
|
||||||
|
reset()
|
||||||
|
printerr("No node named %s has been loaded" % name)
|
||||||
|
return false
|
||||||
|
|
||||||
|
_dialogue.dlog("Running node %s" % name)
|
||||||
|
|
||||||
|
_currentNode = _program.yarnNodes[name]
|
||||||
|
reset()
|
||||||
|
_state.currentNodeName = name
|
||||||
|
nodeStartHandler.call_func(name)
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
func current_node_name()->String:
|
||||||
|
return _currentNode.nodeName
|
||||||
|
|
||||||
|
func current_node():
|
||||||
|
return _currentNode
|
||||||
|
|
||||||
|
func pause():
|
||||||
|
executionState = YarnGlobals.ExecutionState.Suspended
|
||||||
|
|
||||||
|
#stop exectuion
|
||||||
|
func stop():
|
||||||
|
executionState = YarnGlobals.ExecutionState.Stopped
|
||||||
|
reset()
|
||||||
|
_currentNode = null
|
||||||
|
|
||||||
|
#set the currently selected option and
|
||||||
|
#resume execution if waiting for result
|
||||||
|
#return false if error
|
||||||
|
func set_selected_option(id):
|
||||||
|
if executionState != YarnGlobals.ExecutionState.WaitingForOption:
|
||||||
|
printerr("Unable to select option when dialogue not waiting for option")
|
||||||
|
return false
|
||||||
|
|
||||||
|
if id < 0 || id >= _state.currentOptions.size():
|
||||||
|
printerr("%d is not a valid option "%id)
|
||||||
|
return false
|
||||||
|
|
||||||
|
var destination = _state.currentOptions[id].value
|
||||||
|
_state.push_value(destination)
|
||||||
|
_state.currentOptions.clear()
|
||||||
|
|
||||||
|
#no longer waiting for option
|
||||||
|
executionState = YarnGlobals.ExecutionState.Suspended
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
func has_options()->bool:
|
||||||
|
return _state.currentOptions.size() > 0
|
||||||
|
|
||||||
|
func reset():
|
||||||
|
_state = VmState.new()
|
||||||
|
|
||||||
|
#continue execution
|
||||||
|
func resume()->bool:
|
||||||
|
if _currentNode == null :
|
||||||
|
printerr("Cannot run dialogue with no node selected")
|
||||||
|
return false
|
||||||
|
if executionState == YarnGlobals.ExecutionState.WaitingForOption:
|
||||||
|
printerr("Cannot run while waiting for option")
|
||||||
|
return false
|
||||||
|
|
||||||
|
if lineHandler == null :
|
||||||
|
printerr("Cannot run without a lineHandler")
|
||||||
|
return false
|
||||||
|
|
||||||
|
if optionsHandler == null :
|
||||||
|
printerr("Cannot run without an optionsHandler")
|
||||||
|
return false
|
||||||
|
|
||||||
|
if commandHandler == null :
|
||||||
|
printerr("Cannot run without an commandHandler")
|
||||||
|
return false
|
||||||
|
if nodeStartHandler == null :
|
||||||
|
printerr("Cannot run without a nodeStartHandler")
|
||||||
|
return false
|
||||||
|
if nodeCompleteHandler == null :
|
||||||
|
printerr("Cannot run without an nodeCompleteHandler")
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
|
executionState = YarnGlobals.ExecutionState.Running
|
||||||
|
|
||||||
|
#execute instruction until something cool happens
|
||||||
|
while executionState == YarnGlobals.ExecutionState.Running:
|
||||||
|
var currentInstruction = _currentNode.instructions[_state.programCounter]
|
||||||
|
|
||||||
|
run_instruction(currentInstruction)
|
||||||
|
_state.programCounter+=1
|
||||||
|
|
||||||
|
if _state.programCounter >= _currentNode.instructions.size():
|
||||||
|
nodeCompleteHandler.call_func(_currentNode.nodeName)
|
||||||
|
executionState = YarnGlobals.ExecutionState.Stopped
|
||||||
|
reset()
|
||||||
|
dialogueCompleteHandler.call_func()
|
||||||
|
_dialogue.dlog("Run Complete")
|
||||||
|
|
||||||
|
return true
|
||||||
|
|
||||||
|
func find_label_instruction(label:String)->int:
|
||||||
|
if !_currentNode.labels.has(label):
|
||||||
|
printerr("Unknown label:"+label)
|
||||||
|
return -1
|
||||||
|
return _currentNode.labels[label]
|
||||||
|
|
||||||
|
func run_instruction(instruction)->bool:
|
||||||
|
match instruction.operation:
|
||||||
|
YarnGlobals.ByteCode.Label:
|
||||||
|
#do nothing woooo!
|
||||||
|
pass
|
||||||
|
YarnGlobals.ByteCode.JumpTo:
|
||||||
|
#jump to named label
|
||||||
|
_state .programCounter = find_label_instruction(instruction.operands[0].value)-1
|
||||||
|
YarnGlobals.ByteCode.RunLine:
|
||||||
|
#look up string from string table
|
||||||
|
#pass it to client as line
|
||||||
|
var key = instruction.operands[0].value
|
||||||
|
|
||||||
|
var line = Line.new(key, _program.yarnStrings[key])
|
||||||
|
|
||||||
|
#the second operand is the expression count
|
||||||
|
# of format function
|
||||||
|
if instruction.operands.size() > 1:
|
||||||
|
pass#add format function support
|
||||||
|
|
||||||
|
var pause : int = lineHandler.call_func(line)
|
||||||
|
|
||||||
|
|
||||||
|
if pause == YarnGlobals.HandlerState.PauseExecution:
|
||||||
|
executionState = YarnGlobals.ExecutionState.Suspended
|
||||||
|
|
||||||
|
|
||||||
|
YarnGlobals.ByteCode.RunCommand:
|
||||||
|
var commandText : String = instruction.operands[0].value
|
||||||
|
|
||||||
|
if instruction.operands.size() > 1:
|
||||||
|
pass#add format function
|
||||||
|
|
||||||
|
var command = Command.new(commandText)
|
||||||
|
|
||||||
|
var pause = commandHandler.call_func(command) as int
|
||||||
|
if pause == YarnGlobals.HandlerState.PauseExecution:
|
||||||
|
executionState = YarnGlobals.ExecutionState.Suspended
|
||||||
|
|
||||||
|
|
||||||
|
YarnGlobals.ByteCode.PushString:
|
||||||
|
#push String var to stack
|
||||||
|
_state.push_value(instruction.operands[0].value)
|
||||||
|
YarnGlobals.ByteCode.PushNumber:
|
||||||
|
#push number to stack
|
||||||
|
_state.push_value(instruction.operands[0].value)
|
||||||
|
YarnGlobals.ByteCode.PushBool:
|
||||||
|
#push boolean to stack
|
||||||
|
_state.push_value(instruction.operands[0].value)
|
||||||
|
|
||||||
|
YarnGlobals.ByteCode.PushNull:
|
||||||
|
#push null t
|
||||||
|
_state.push_value(NULL_VALUE)
|
||||||
|
|
||||||
|
YarnGlobals.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
|
||||||
|
|
||||||
|
YarnGlobals.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
|
||||||
|
YarnGlobals.ByteCode.Pop:
|
||||||
|
#pop value from stack
|
||||||
|
_state.pop_value()
|
||||||
|
YarnGlobals.ByteCode.CallFunc:
|
||||||
|
#call function with params on stack
|
||||||
|
#push any return value to stack
|
||||||
|
var functionName : String = instruction.operands[0].value
|
||||||
|
|
||||||
|
var function = _dialogue.library.get_function(functionName)
|
||||||
|
|
||||||
|
var expectedParamCount : int = function.paramCount
|
||||||
|
var actualParamCount : 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 expectedParamCount == -1:
|
||||||
|
expectedParamCount = actualParamCount
|
||||||
|
|
||||||
|
if expectedParamCount != actualParamCount:
|
||||||
|
printerr("Function %s expected %d parameters but got %d instead" %[functionName,
|
||||||
|
expectedParamCount,actualParamCount])
|
||||||
|
return false
|
||||||
|
|
||||||
|
var result
|
||||||
|
|
||||||
|
if actualParamCount == 0:
|
||||||
|
result = function.invoke()
|
||||||
|
else:
|
||||||
|
var params : Array = []#value
|
||||||
|
for i in range(actualParamCount):
|
||||||
|
params.push_front(_state.pop_value())
|
||||||
|
|
||||||
|
result = function.invoke(params)
|
||||||
|
|
||||||
|
if function.returnsValue:
|
||||||
|
_state.push_value(result)
|
||||||
|
pass
|
||||||
|
|
||||||
|
YarnGlobals.ByteCode.PushVariable:
|
||||||
|
#get content of variable and push to stack
|
||||||
|
var name : String = instruction.operands[0].value
|
||||||
|
var loaded = _dialogue._variableStorage.get_value(name)
|
||||||
|
_state.push_value(loaded)
|
||||||
|
YarnGlobals.ByteCode.StoreVariable:
|
||||||
|
#store top stack value to variable
|
||||||
|
var top = _state.peek_value()
|
||||||
|
var destination : String = instruction.operands[0].value
|
||||||
|
_dialogue._variableStorage.set_value(destination,top)
|
||||||
|
|
||||||
|
YarnGlobals.ByteCode.Stop:
|
||||||
|
#stop execution and repost it
|
||||||
|
nodeCompleteHandler.call_func(_currentNode.nodeName)
|
||||||
|
dialogueCompleteHandler.call_func()
|
||||||
|
executionState = YarnGlobals.ExecutionState.Stopped
|
||||||
|
reset()
|
||||||
|
|
||||||
|
YarnGlobals.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 :
|
||||||
|
name = instruction.operands[0].value
|
||||||
|
|
||||||
|
var pause = nodeCompleteHandler.call_func(_currentNode.nodeName)
|
||||||
|
set_node(name)
|
||||||
|
_state.programCounter-=1
|
||||||
|
if pause == YarnGlobals.HandlerState.PauseExecution:
|
||||||
|
executionState = YarnGlobals.ExecutionState.Suspended
|
||||||
|
|
||||||
|
YarnGlobals.ByteCode.AddOption:
|
||||||
|
# add an option to current state
|
||||||
|
var key = instruction.operands[0].value
|
||||||
|
|
||||||
|
var line = Line.new(key, _program.yarnStrings[key])
|
||||||
|
|
||||||
|
if instruction.operands.size() > 2:
|
||||||
|
pass #formated text options
|
||||||
|
|
||||||
|
# line to show and node name
|
||||||
|
_state.currentOptions.append(SimpleEntry.new(line,instruction.operands[1].value))
|
||||||
|
YarnGlobals.ByteCode.ShowOptions:
|
||||||
|
#show options - stop if none
|
||||||
|
if _state.currentOptions.size() == 0:
|
||||||
|
executionState = YarnGlobals.ExecutionState.Stopped
|
||||||
|
reset()
|
||||||
|
dialogueCompleteHandler.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(Option.new(option.key, optionIndex, option.value))
|
||||||
|
|
||||||
|
#we cant continue until option chosen
|
||||||
|
executionState = YarnGlobals.ExecutionState.WaitingForOption
|
||||||
|
|
||||||
|
#pass the options to the client
|
||||||
|
#delegate for them to call
|
||||||
|
#when user makes selection
|
||||||
|
|
||||||
|
optionsHandler.call_func(choices)
|
||||||
|
pass
|
||||||
|
_:
|
||||||
|
#bytecode messed up woopsise
|
||||||
|
executionState = YarnGlobals.ExecutionState.Stopped
|
||||||
|
reset()
|
||||||
|
printerr("Unknown Bytecode %s "%instruction.operation)
|
||||||
|
return false
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
func push_value(value)->void:
|
||||||
|
if value is Value:
|
||||||
|
stack.push_back(value)
|
||||||
|
else:
|
||||||
|
stack.push_back(Value.new(value))
|
||||||
|
|
||||||
|
|
||||||
|
func pop_value():
|
||||||
|
return stack.pop_back()
|
||||||
|
|
||||||
|
func peek_value():
|
||||||
|
return stack.back()
|
||||||
|
|
||||||
|
func clear_stack():
|
||||||
|
stack.clear()
|
||||||
|
|
||||||
|
class SimpleEntry:
|
||||||
|
var key
|
||||||
|
var value : String
|
||||||
|
|
||||||
|
func _init(key,value:String):
|
||||||
|
self.key = key
|
||||||
|
self.value = value
|
33
addons/Wol/examples/example1.yarn
Normal file
33
addons/Wol/examples/example1.yarn
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
title: Start
|
||||||
|
tags:
|
||||||
|
colorID: 3
|
||||||
|
position: 332,79
|
||||||
|
---
|
||||||
|
Oh well hello, what should we set number variable to?
|
||||||
|
<<set $visited = visit_count()>>
|
||||||
|
-> Set Number to 6
|
||||||
|
<<set $number to 6 >>
|
||||||
|
-> Set Number to 5
|
||||||
|
<<set $number to 5 >>
|
||||||
|
We will also set a cat boolean
|
||||||
|
-> to false
|
||||||
|
<<set $cat to false>>
|
||||||
|
-> to true
|
||||||
|
<<set $cat to true>>
|
||||||
|
[[SecondNode]]
|
||||||
|
===
|
||||||
|
title: SecondNode
|
||||||
|
tags:
|
||||||
|
position: 678.16,263.66
|
||||||
|
---
|
||||||
|
sweet - Now we are in the Second node
|
||||||
|
->this one only shows when Visit count of first node is 1 <<if $visited == 1>>
|
||||||
|
->This only options is showing because cat variable is true <<if $cat == true>>
|
||||||
|
ok then I guess cat is true
|
||||||
|
->Some other option with no requirement
|
||||||
|
this is some other option woooooohooo
|
||||||
|
->This shows when number greater than 5 <<if $number > 5 >>
|
||||||
|
Coool, yes the number was greater than 5 <<set $number to 1>>
|
||||||
|
But not anymore! ha
|
||||||
|
Now we are finished, GoodBye!
|
||||||
|
===
|
6
addons/Wol/plugin.cfg
Normal file
6
addons/Wol/plugin.cfg
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[plugin]
|
||||||
|
name="Wol"
|
||||||
|
description="A dialogue system for Godot based on YarnSpinner"
|
||||||
|
author="Bram Dingelstad (@bram_dingelstad)"
|
||||||
|
version="0.0.0"
|
||||||
|
script="plugin.gd"
|
20
addons/Wol/plugin.gd
Normal file
20
addons/Wol/plugin.gd
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
tool
|
||||||
|
extends EditorPlugin
|
||||||
|
|
||||||
|
func _enter_tree():
|
||||||
|
add_autoload_singleton(
|
||||||
|
'YarnGlobals',
|
||||||
|
'res://addons/Wol/autoloads/execution_states.gd'
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_type(
|
||||||
|
'Wol',
|
||||||
|
'Node',
|
||||||
|
load('res://addons/Wol/yarn_runner.gd'),
|
||||||
|
load('res://addons/Wol/assets/icon.png')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _exit_tree():
|
||||||
|
remove_autoload_singleton('YarnGlobals')
|
||||||
|
remove_custom_type('Wol')
|
120
addons/Wol/yarn_runner.gd
Normal file
120
addons/Wol/yarn_runner.gd
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
tool
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
signal node_started(node)
|
||||||
|
signal line(line)
|
||||||
|
signal options(options)
|
||||||
|
signal command(command)
|
||||||
|
signal node_completed(node)
|
||||||
|
|
||||||
|
signal running
|
||||||
|
signal finished
|
||||||
|
|
||||||
|
const YarnCompiler = preload("res://addons/Wol/core/compiler/compiler.gd")
|
||||||
|
const YarnDialogue = preload("res://addons/Wol/core/dialogue.gd")
|
||||||
|
|
||||||
|
export(String, FILE, "*.yarn") var path setget set_path
|
||||||
|
export(String) var start_node = "Start"
|
||||||
|
export(bool) var auto_start = false
|
||||||
|
export(NodePath) var variable_storage_path
|
||||||
|
|
||||||
|
onready var variable_storage = get_node(variable_storage_path)
|
||||||
|
|
||||||
|
var program
|
||||||
|
|
||||||
|
var dialogue
|
||||||
|
var running = false
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
if Engine.editor_hint:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not variable_storage:
|
||||||
|
variable_storage = Node.new()
|
||||||
|
variable_storage.name = 'VariableStorage'
|
||||||
|
variable_storage.set_script(load('res://addons/Wol/core/variable_storage.gd'))
|
||||||
|
add_child(variable_storage)
|
||||||
|
|
||||||
|
if auto_start:
|
||||||
|
start()
|
||||||
|
|
||||||
|
func init_dialogue():
|
||||||
|
# FIXME: Move visited count to variable storage
|
||||||
|
var existing_state
|
||||||
|
if dialogue != null:
|
||||||
|
existing_state = dialogue._visitedNodeCount
|
||||||
|
|
||||||
|
dialogue = YarnDialogue.new(variable_storage)
|
||||||
|
|
||||||
|
# FIXME: Remove these lines
|
||||||
|
if existing_state:
|
||||||
|
dialogue._visitedNodeCount = existing_state
|
||||||
|
|
||||||
|
dialogue.get_vm().lineHandler = funcref(self, "_handle_line")
|
||||||
|
dialogue.get_vm().optionsHandler = funcref(self, "_handle_options")
|
||||||
|
dialogue.get_vm().commandHandler = funcref(self, "_handle_command")
|
||||||
|
dialogue.get_vm().nodeCompleteHandler = funcref(self, "_handle_node_complete")
|
||||||
|
dialogue.get_vm().dialogueCompleteHandler = funcref(self, "_handle_dialogue_complete")
|
||||||
|
dialogue.get_vm().nodeStartHandler = funcref(self, "_handle_node_start")
|
||||||
|
|
||||||
|
dialogue.set_program(program)
|
||||||
|
|
||||||
|
func set_path(_path):
|
||||||
|
var file = File.new()
|
||||||
|
file.open(_path, File.READ)
|
||||||
|
var source = file.get_as_text()
|
||||||
|
file.close()
|
||||||
|
program = YarnCompiler.compile_string(source, _path)
|
||||||
|
path = _path
|
||||||
|
|
||||||
|
func _handle_line(line):
|
||||||
|
call_deferred('emit_signal', 'line', line)
|
||||||
|
return YarnGlobals.HandlerState.PauseExecution
|
||||||
|
|
||||||
|
func _handle_command(command):
|
||||||
|
call_deferred('emit_signal', 'command', command)
|
||||||
|
return YarnGlobals.HandlerState.PauseExecution
|
||||||
|
|
||||||
|
func _handle_options(options):
|
||||||
|
emit_signal('options', options)
|
||||||
|
return YarnGlobals.HandlerState.PauseExecution
|
||||||
|
|
||||||
|
func _handle_dialogue_complete():
|
||||||
|
emit_signal('finished')
|
||||||
|
running = false
|
||||||
|
|
||||||
|
func _handle_node_start(node):
|
||||||
|
emit_signal('node_started', node)
|
||||||
|
print('node started')
|
||||||
|
dialogue.resume()
|
||||||
|
|
||||||
|
if !dialogue._visitedNodeCount.has(node):
|
||||||
|
dialogue._visitedNodeCount[node] = 1
|
||||||
|
else:
|
||||||
|
dialogue._visitedNodeCount[node] += 1
|
||||||
|
|
||||||
|
print(dialogue._visitedNodeCount)
|
||||||
|
|
||||||
|
func _handle_node_complete(node):
|
||||||
|
emit_signal('node_completed', node)
|
||||||
|
running = false
|
||||||
|
return YarnGlobals.HandlerState.ContinueExecution
|
||||||
|
|
||||||
|
func select_option(id):
|
||||||
|
dialogue.get_vm().set_selected_option(id)
|
||||||
|
|
||||||
|
func pause():
|
||||||
|
dialogue.call_deferred('pause')
|
||||||
|
|
||||||
|
func start(node = start_node):
|
||||||
|
if running:
|
||||||
|
return
|
||||||
|
|
||||||
|
init_dialogue()
|
||||||
|
emit_signal('running')
|
||||||
|
|
||||||
|
running = true
|
||||||
|
dialogue.set_node(node)
|
||||||
|
|
||||||
|
func resume():
|
||||||
|
dialogue.call_deferred('resume')
|
7
default_env.tres
Normal file
7
default_env.tres
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
[gd_resource type="Environment" load_steps=2 format=2]
|
||||||
|
|
||||||
|
[sub_resource type="ProceduralSky" id=1]
|
||||||
|
|
||||||
|
[resource]
|
||||||
|
background_mode = 2
|
||||||
|
background_sky = SubResource( 1 )
|
319
dialogue.yarn
Normal file
319
dialogue.yarn
Normal file
|
@ -0,0 +1,319 @@
|
||||||
|
title: Waking up
|
||||||
|
tags:
|
||||||
|
colorID: 0
|
||||||
|
position: -1544,-923
|
||||||
|
---
|
||||||
|
<<fade_out>>
|
||||||
|
<<go_to_space Beginning Room>>
|
||||||
|
<<protagonist Kiwa>>
|
||||||
|
<<set $something to 1>>
|
||||||
|
|
||||||
|
>.<
|
||||||
|
>…<
|
||||||
|
>…<
|
||||||
|
>I woke up to a pulsating pain in my head.<
|
||||||
|
>It hurts.<
|
||||||
|
>It hurts a lot.<
|
||||||
|
>It was like someone was hammering an ice pick right into my head and chiselling my brain into multiple chunks at once.<
|
||||||
|
>I tried to reach my head in an attempt to cradle it, hoping I could just slump into a fetal position and wait for the pain to go away.<
|
||||||
|
>But I couldn’t move my arms.<
|
||||||
|
>Something was keeping them in place.<
|
||||||
|
>Something cold and really sturdy.<
|
||||||
|
>Then the implications of that set in.<
|
||||||
|
>And I shot my eyes wide open.<
|
||||||
|
>But all I could see was [p;pink].<
|
||||||
|
>So much [p;pink].<
|
||||||
|
>And a figure sitting in front of me.<
|
||||||
|
|
||||||
|
//<<opening_screen prologue>>
|
||||||
|
<<toggle_dialogue off>>
|
||||||
|
<<picture Prologue title>>
|
||||||
|
<<fade_in>>
|
||||||
|
<<wait 3>>
|
||||||
|
<<fade_out>>
|
||||||
|
<<picture Kiwa looking at mirror>>
|
||||||
|
<<fade_in>>
|
||||||
|
<<toggle_dialogue on>>
|
||||||
|
|
||||||
|
>Ah… that’s just me. Dang, I almost got startled by my own reflection.<
|
||||||
|
>Why is there a mirror in front of me anyway?<
|
||||||
|
>I looked down at my reflection’s hands.<
|
||||||
|
>Yeah, I was definitely tied up alright. Fun.<
|
||||||
|
>I tried to yank my arm out of the handcuffs in hopes of a poor job done by whoever had gotten me in this situation.<
|
||||||
|
>After a few pulling attempts all I got was a bruised wrist. It was a long shot anyways.<
|
||||||
|
>Okay… What can I do in this situation?<
|
||||||
|
>I looked around me.<
|
||||||
|
>This looks like a welcome party or something… Except for all those chairs straight from an old timey asylum.<
|
||||||
|
>The chairs looked pretty much like mine, meaning-<
|
||||||
|
???: Ugh…
|
||||||
|
>The sound came from behind me.<
|
||||||
|
>I tried to crane my neck to look at the source of the voice but a dull thud accompanied by the feeling of my helmet bouncing against something stopped me.<
|
||||||
|
???: Ow!
|
||||||
|
>A long string of surprisingly mild but creative curses echoed in the room.<
|
||||||
|
???: Was that really necessary?
|
||||||
|
>I couldn’t see much from the angle I was in but from the voice I figured a man around my age was sitting with his back against mine.<
|
||||||
|
>The mirror wasn’t much help either. All I could see was a little bit of dark auburn hair.<
|
||||||
|
>It seems like I didn’t learn from the first attempt and tried to turn my head again to see the source of the voice.<
|
||||||
|
???: OW! Christ! Please stop hitting your head against mine!
|
||||||
|
Kiwa: Oh, my bad?
|
||||||
|
???: Who wears a helmet inside?
|
||||||
|
Kiwa: Precautions?
|
||||||
|
Kiwa: I mean, my head doesn’t have a bump right now, so I guess that’s a testimony to it.
|
||||||
|
???: And mine was hurting enough as it was.
|
||||||
|
Kiwa: Uh?
|
||||||
|
|
||||||
|
<<hide_pictures>>
|
||||||
|
<<zoom to VentLid>>
|
||||||
|
|
||||||
|
???: What’s happening? I can’t see from this position.
|
||||||
|
Kiwa: A… a countdown?
|
||||||
|
Kiwa: What does this mean?
|
||||||
|
>I had a bad feeling. A feeling that solidified into a webbed dark lump inside my chest.<
|
||||||
|
>My mind was screaming at me to run away as the countdown neared zero.<
|
||||||
|
-PAIR 8, STARTING PREPARATIONS-
|
||||||
|
Kiwa: Starting... Wha-
|
||||||
|
-Click-
|
||||||
|
???: The restraints!
|
||||||
|
>We scrambled off the chairs as quickly as humanly possible.<
|
||||||
|
|
||||||
|
<<switch_to_camera PanningRoomCamera>>
|
||||||
|
|
||||||
|
???: ...
|
||||||
|
Kiwa: Wait, I’d be amazed if that door is open by any chance.
|
||||||
|
-Click Click-
|
||||||
|
???: It really is locked.
|
||||||
|
???: I figured I had nothing to lose by trying.
|
||||||
|
Kiwa: We’ll just have to wait then. What is this room anyway?
|
||||||
|
???: All I can say is this looks rather dubious.
|
||||||
|
Kiwa: Hmm.
|
||||||
|
-Welcome esteemed guests! The game you have been dying to participate in has begun!-
|
||||||
|
-But first, let us get you familiar with the control scheme-
|
||||||
|
-I know this can get quite tedious to a brilliant mind such as yours truly, but some of the controls and mechanics may vary from the original games.-
|
||||||
|
-By pressing [p;Shift], you can see circles around objects or characters that indicate there’s dialogue to be explored. [b;Blue circles] are optional dialogue while [y; yellow circles] will progress the plot.-
|
||||||
|
-If a scene has multiple [y;yellow circles], you need to exhaust all yellow dialogue options before you can progress with the plot.-
|
||||||
|
-[p;Ctrl] opens and closes the transcript in case you need to review previous dialogue.-
|
||||||
|
-Now then, explore your first area to your heart’s content, participant.-
|
||||||
|
|
||||||
|
<<switch_to_camera PanningRoomCamera>>
|
||||||
|
<<detective_mode>>
|
||||||
|
===
|
||||||
|
title: The Revolver
|
||||||
|
tags:
|
||||||
|
colorID: 0
|
||||||
|
position: -1892,-1013
|
||||||
|
---
|
||||||
|
<<load_situation The Revolver>>
|
||||||
|
<<animation elevator door open>>
|
||||||
|
|
||||||
|
Masami: Gina, this way!
|
||||||
|
Gina: Patience! I’m coming.
|
||||||
|
>Still, she didn’t pick up her pace and kept hesitantly looking around.<
|
||||||
|
Masami: Everything should be fine, let’s just go. We won.
|
||||||
|
Masami: Unless you are planning on backstabbing me at the finish line, that is.
|
||||||
|
Gina: How funny. You are not that special.
|
||||||
|
Masami: Har har. Now start walking or I’ll carry you out the front door.
|
||||||
|
Masami: I don’t want to give Monokuma the time to come up with a new death game for two while we are trying to get through the exit.
|
||||||
|
Gina: …
|
||||||
|
|
||||||
|
<<zoom to vault door>>
|
||||||
|
|
||||||
|
Masami: Wait, the vault door… It’s not open.
|
||||||
|
Gina: So it seems.
|
||||||
|
Masami: But that doesn’t make sense! So far we have opened one lock per two deceased. All seven locks should be open by now.
|
||||||
|
>In desperation I tried the vault handle. I wasn’t exceptionally strong, but I doubted even a weightlifter could open this door whether it was held shut by one or dozen locks.<
|
||||||
|
Gina: Rather than using your energy by tearing at the 700kg door-
|
||||||
|
Masami: I wasn’t trying to brute force it.
|
||||||
|
Gina: Anyway, I would suggest you spare a glance at the reception desk. It appears the staff has given us one last gift.
|
||||||
|
Masami: The reception desk?
|
||||||
|
|
||||||
|
<<zoom to gun>>
|
||||||
|
|
||||||
|
Masami: A paper and…
|
||||||
|
Masami: … That’s a gun.
|
||||||
|
Gina: A revolver, in fact. Such an old fashioned choice of firearm. Though, I appreciate the aesthetic. How pretty.
|
||||||
|
Masami: I don’t like this. Why is it here?
|
||||||
|
>I took the paper from the table and briskly unfolded it.<
|
||||||
|
- Congratulations on reaching the final part of the game!-
|
||||||
|
- Unfortunately, no one remembers a silver medalist.-
|
||||||
|
- Let’s round up the game with a match of our favorite luck game! Russian roulette should be simple enough.-
|
||||||
|
- Now then, ladies first!-
|
||||||
|
-Staff-
|
||||||
|
Masami: Russian roulette… Are you kidding me?
|
||||||
|
Masami: This can’t be part of the game. This wasn’t in the rules!
|
||||||
|
Masami: I know the winning condition hasn’t been met but-
|
||||||
|
Gina: It’s rigged.
|
||||||
|
Masami: What?
|
||||||
|
>Gina had picked up the revolver from the table and without better judgement, opened the cylinder for inspection.<
|
||||||
|
Gina: It’s rigged.
|
||||||
|
Masami: ... “Ladies first.”
|
||||||
|
Masami: The cylinder is full, isn't it?
|
||||||
|
>Gina let out a tired sigh. Her shoulders slumping heavily from disappointment.<
|
||||||
|
Gina: I got so far.
|
||||||
|
Gina: I don’t know what led me to believe the outcome would be any different.
|
||||||
|
Gina: This is such a waste of resources as well.
|
||||||
|
Masami: Gina, put the revolver down. If the game is rigged, you don’t have to play. The rules won’t matter then.
|
||||||
|
Gina: Let it rest.
|
||||||
|
Gina: Like any of it matters.
|
||||||
|
Masami: What do you mean?
|
||||||
|
>She didn’t answer.<
|
||||||
|
>Instead, she absentmindedly fiddled with the cylinder before locking it in place.<
|
||||||
|
>Then she spun the cylinder for the hell of it before raising the revolver’s barrel against her very own temple.<
|
||||||
|
>She looked resigned. Though I couldn’t help but notice the slight glint of fear in her eyes.<
|
||||||
|
>None of us wanted to die.<
|
||||||
|
Masami: Gina, please.
|
||||||
|
Gina: Congratulations.
|
||||||
|
Masami: Gina!
|
||||||
|
<<sound revolver_bang>>
|
||||||
|
<<fade_out>>
|
||||||
|
|
||||||
|
[[Waking up]]
|
||||||
|
===
|
||||||
|
title: Screen
|
||||||
|
tags:
|
||||||
|
colorID: 0
|
||||||
|
position: -1170,-560
|
||||||
|
---
|
||||||
|
<<zoom to Screen>>
|
||||||
|
???: Pair 8?
|
||||||
|
Kiwa: Us, probably.
|
||||||
|
???: What are they preparing us for exactly?
|
||||||
|
Kiwa: I don’t think I want to find out.
|
||||||
|
Kiwa: The countdown already made me nervous.
|
||||||
|
===
|
||||||
|
title: Chairs
|
||||||
|
tags:
|
||||||
|
colorID: 0
|
||||||
|
position: -874,-551
|
||||||
|
---
|
||||||
|
<<zoom to Chairs>>
|
||||||
|
Kiwa: That’s a lot of chairs.
|
||||||
|
???: 16 of them…
|
||||||
|
Kiwa: Were we the last to wake up?
|
||||||
|
???: My question is where are the other 14 people?
|
||||||
|
???: What happened to them?
|
||||||
|
===
|
||||||
|
title: Decorations
|
||||||
|
tags:
|
||||||
|
colorID: 0
|
||||||
|
position: -593,-554
|
||||||
|
---
|
||||||
|
<<zoom to Decorations>>
|
||||||
|
Kiwa: That’s… a lot of hearts.
|
||||||
|
???: My eyes hurt just from looking at all of this. It feels like a crime to have this many shades of pink together at once.
|
||||||
|
Kiwa: It’s definitely ugly.
|
||||||
|
===
|
||||||
|
title: Vent
|
||||||
|
tags:
|
||||||
|
colorID: 0
|
||||||
|
position: -338,-536
|
||||||
|
---
|
||||||
|
<<zoom to Vent>>
|
||||||
|
Kiwa: Do you think we could get out through that vent?
|
||||||
|
???: I don’t even know how we could reach it. Let alone open it.
|
||||||
|
Kiwa: Yeah, the chairs look like they have been bolted to the floor.
|
||||||
|
Kiwa: Also, what’s with this smell? It’s really sweet. Like someone dumped a bag of sugar into an air filter.
|
||||||
|
???: You are right. It’s quite overpowering. I hope it won’t make me sick.
|
||||||
|
===
|
||||||
|
title: Mirror
|
||||||
|
tags:
|
||||||
|
colorID: 0
|
||||||
|
position: -1438,-573
|
||||||
|
---
|
||||||
|
<<zoom to Mirror>>
|
||||||
|
>I look like I always look. A disheveled hobo skater who got severely dunked on recently.<
|
||||||
|
???: Admiring yourself in the mirror?
|
||||||
|
Kiwa: Not really.
|
||||||
|
???: I wonder why there are so many mirrors in this room. It feels like an interactive exhibit of a contemporary art gallery or something along those lines.
|
||||||
|
Kiwa: Or a fun house without those wobbly mirrors.
|
||||||
|
???: Or that...
|
||||||
|
===
|
||||||
|
title: Ending
|
||||||
|
tags:
|
||||||
|
colorID: 0
|
||||||
|
position: -139,-184
|
||||||
|
---
|
||||||
|
<<if !(visited("Mirror") and visited("Vent") and visited("Chairs") and visited("Decorations") and visited("Screen"))>>
|
||||||
|
<<if visit_count() is 2>>
|
||||||
|
-You are a hopeless cause.-
|
||||||
|
<<else>>
|
||||||
|
???: ...
|
||||||
|
Kiwa: He's looking around the room. Maybe I should do the same. Access my surroundings and all that...
|
||||||
|
- ...Hmm. Like I previously established, please take a look at your surroundings.-
|
||||||
|
-By pressing [p;Shift], you can see circles around objects or characters that indicate there’s dialogue to be explored. [b;Blue circles] are optional dialogue while [y; yellow circles] will progress the plot.-
|
||||||
|
-If a scene has multiple [y;yellow circles], you need to exhaust all yellow dialogue options before you can progress with the plot.-
|
||||||
|
-[p;Ctrl] opens and closes the transcript in case you need to review previous dialogue.-
|
||||||
|
-You can move around the room with the [p;A] and [p;D] keys.-
|
||||||
|
-Do not make me repeat myself again.-
|
||||||
|
<<endif>>
|
||||||
|
<<else>>
|
||||||
|
>Well, there isn’t much else to look at. Maybe I should try to talk to him more. He might know something.<
|
||||||
|
>I guess I’ll try to make light of the situation. That works with lowering stressful atmosphere, right?<
|
||||||
|
Kiwa: Uh, so… Do you come around here often?
|
||||||
|
???: Seriously? In this situation?
|
||||||
|
Kiwa: It was just a joke? I think?
|
||||||
|
???: You are taking this very nonchalantly.
|
||||||
|
???: And by this, I mean the obvious kidnapping for… for…
|
||||||
|
???: I don’t know. Hopefully just hostage money.
|
||||||
|
Kiwa: Maybe we’ll get sold for the black market.
|
||||||
|
???: Oh god. Hopefully not.
|
||||||
|
Kiwa: Anyway, what sort of torture chamber is covered in pink hearts?
|
||||||
|
???: I don’t even want to entertain the implications.
|
||||||
|
???: Let’s just say the uncomfortable kind.
|
||||||
|
>He suddenly perked up and started to pat down his pant pockets before his stature deflated again.<
|
||||||
|
???: Of course they would empty our pockets. What’s left of my belongings is some spare change and pocket lint.
|
||||||
|
Kiwa: Oh.
|
||||||
|
>I quickly checked my own pockets.<
|
||||||
|
Kiwa: You are right. No phone, or keys... or wallet.
|
||||||
|
Kiwa: They only left me with my lighter but no cigarettes. Why would they take those away but not the lighter?
|
||||||
|
???: So arson is fine but they draw the line at lung abuse?
|
||||||
|
Kiwa: This is going to be a problem… mainly for me.
|
||||||
|
Kiwa: Well, if it comes to it, I can at least set this room on fire.
|
||||||
|
???: Please don’t. I don’t want to get grouped in with your lawsuit.
|
||||||
|
>That’s the part you are worried about?<
|
||||||
|
???: Are you some sort of criminal?
|
||||||
|
Kiwa: Something along those lines, sure.
|
||||||
|
???: I’m assuming a petty one at that then.
|
||||||
|
Kiwa: Yeah.
|
||||||
|
Kiwa: Sorry, I guess introductions weren’t the first thing on my mind.
|
||||||
|
Kiwa: I’m Kiwa Fukuda, the former ultimate scapegoat.
|
||||||
|
???: What a title. And you were an ultimate to boot.
|
||||||
|
???: How come I have never heard of you?
|
||||||
|
Kiwa: I try to keep a low profile. That works best with what I do.
|
||||||
|
???: What exactly does a scapegoat do for a living?
|
||||||
|
Kiwa: I’m really good at pointing fingers at people.
|
||||||
|
???: Meaning?
|
||||||
|
Kiwa: If you have a problem and you don’t want to deal with it like a respectable member of society, you come to me. I will either make it look like I fucked up the whole situation or I make someone else who is more suited for the situation look like they were at fault.
|
||||||
|
???: How do you find out you have such a talent in the first place?
|
||||||
|
Kiwa: With a lot of stupid actions, a lot of accidents, and a lot of dumb luck.
|
||||||
|
Kiwa: It can get very messy sometimes but it pays my rent three times over.
|
||||||
|
Kiwa: There are a lot of people who would like to have my head for less than one yen, so I wouldn’t be surprised if this whole kidnapping thing was my fault.
|
||||||
|
Kiwa: Though I don’t get why you would be in the same situation then? I have never seen you in my life.
|
||||||
|
Kiwa: Who are you?
|
||||||
|
???: …Right.
|
||||||
|
Masami: My name is Masami Kiyokane, the former ultimate croupier.
|
||||||
|
Kiwa: A croupier?
|
||||||
|
Masami: Basically, I am a card dealer at a casino. The Valentine’s Hand Casino to be exact. You may have heard of it.
|
||||||
|
Kiwa: Ah, yeah. That one is really highly praised. One of the best in the country, right? Never been there myself. I don’t travel much.
|
||||||
|
Masami: Well, I work there nowadays.
|
||||||
|
Masami: My work includes taking bets, distributing cards to the players, and making sure those players don’t cheat among other things.
|
||||||
|
Kiwa: Cool. So, you are a former ultimate yourself?
|
||||||
|
Masami: That’s what that would imply, yes.
|
||||||
|
Kiwa: I really don’t remember you. What class were you in? I didn’t interact much with upper or underclassmen during my time in Hope’s Peak.
|
||||||
|
Masami: 72nd, Class A.
|
||||||
|
Kiwa: Ah, I’m from 71st B. I guess we both didn’t lose our titles that long ago.
|
||||||
|
Masami: Still, like you said. If we don’t even know each other, how are we in this situation?
|
||||||
|
Masami: To be honest, I thought the kidnapping was my fault at the start. But now that I have someone here who runs into legal problems constantly…
|
||||||
|
Kiwa: Why would you think this is your fault?
|
||||||
|
Masami: Well-
|
||||||
|
???: Good afternoon.
|
||||||
|
Masami: What... is that?
|
||||||
|
Kiwa: A bird?
|
||||||
|
???: You have successfully woken up. Good.
|
||||||
|
???: You are expected at the trial grounds. Follow me.
|
||||||
|
>The strange bird turned it’s back to us and started to leave through the open door.<
|
||||||
|
>Figuring out I didn’t exactly have anywhere else to go I started following it. Or tried to, at least.<
|
||||||
|
>A hand grabbed my wrist and forcefully yanked me to run into another direction.<
|
||||||
|
|
||||||
|
<<go_to_space Lobby>>
|
||||||
|
<<endif>>
|
||||||
|
===
|
35
icon.png.import
Normal file
35
icon.png.import
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="StreamTexture"
|
||||||
|
path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://icon.png"
|
||||||
|
dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/hdr_mode=0
|
||||||
|
compress/bptc_ldr=0
|
||||||
|
compress/normal_map=0
|
||||||
|
flags/repeat=0
|
||||||
|
flags/filter=true
|
||||||
|
flags/mipmaps=false
|
||||||
|
flags/anisotropic=false
|
||||||
|
flags/srgb=2
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/HDR_as_SRGB=false
|
||||||
|
process/invert_color=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
stream=false
|
||||||
|
size_limit=0
|
||||||
|
detect_3d=true
|
||||||
|
svg/scale=1.0
|
33
project.godot
Normal file
33
project.godot
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
; Engine configuration file.
|
||||||
|
; It's best edited using the editor UI and not directly,
|
||||||
|
; since the parameters that go here are not all obvious.
|
||||||
|
;
|
||||||
|
; Format:
|
||||||
|
; [section] ; section goes between []
|
||||||
|
; param=value ; assign values to parameters
|
||||||
|
|
||||||
|
config_version=4
|
||||||
|
|
||||||
|
[application]
|
||||||
|
|
||||||
|
config/name="YarnSpinner"
|
||||||
|
config/icon="res://icon.png"
|
||||||
|
|
||||||
|
[autoload]
|
||||||
|
|
||||||
|
YarnGlobals="*res://addons/Wol/autoloads/execution_states.gd"
|
||||||
|
|
||||||
|
[editor_plugins]
|
||||||
|
|
||||||
|
enabled=PoolStringArray( "res://addons/Wol/plugin.cfg" )
|
||||||
|
|
||||||
|
[physics]
|
||||||
|
|
||||||
|
common/enable_pause_aware_picking=true
|
||||||
|
|
||||||
|
[rendering]
|
||||||
|
|
||||||
|
quality/driver/driver_name="GLES2"
|
||||||
|
vram_compression/import_etc=true
|
||||||
|
vram_compression/import_etc2=false
|
||||||
|
environment/default_environment="res://default_env.tres"
|
Reference in a new issue