diff --git a/hank-godot/scripts/Alt.gd b/hank-godot/scripts/Alt.gd index 1466ccb..3753897 100644 --- a/hank-godot/scripts/Alt.gd +++ b/hank-godot/scripts/Alt.gd @@ -1,15 +1,6 @@ class_name Alt -static var behaviorMap: haxe_ds_StringMap = haxe_ds_StringMap.new().__set(">", AltBehavior.AltBehavior.Sequence) -haxe_ds_StringMap.new().__set("!", AltBehavior.AltBehavior.OnceOnly) -haxe_ds_StringMap.new().__set("&", AltBehavior.AltBehavior.Cycle) -haxe_ds_StringMap.new().__set("%", AltBehavior.AltBehavior.Shuffle) -haxe_ds_StringMap.new().__set("sequence:", AltBehavior.AltBehavior.Sequence) -haxe_ds_StringMap.new().__set("once:", AltBehavior.AltBehavior.OnceOnly) -haxe_ds_StringMap.new().__set("cycle:", AltBehavior.AltBehavior.Cycle) -haxe_ds_StringMap.new().__set("shuffle:", AltBehavior.AltBehavior.Shuffle) - -haxe_ds_StringMap.new() +static var behaviorMap: Array[Variant] = [] var behavior: AltBehavior.AltBehavior var outputs: Array[Output] @@ -34,23 +25,53 @@ static func parse(buffer: HankBuffer) -> Variant: tempString = s var expr: String = tempString + + if (behaviorMap.size() == 0): + behaviorMap.push_back({ + "key": ">", + "value": AltBehavior.AltBehavior.Sequence + }) + behaviorMap.push_back({ + "key": "!", + "value": AltBehavior.AltBehavior.OnceOnly + }) + behaviorMap.push_back({ + "key": "&", + "value": AltBehavior.AltBehavior.Cycle + }) + behaviorMap.push_back({ + "key": "%", + "value": AltBehavior.AltBehavior.Shuffle + }) + behaviorMap.push_back({ + "key": "sequence:", + "value": AltBehavior.AltBehavior.Sequence + }) + behaviorMap.push_back({ + "key": "once:", + "value": AltBehavior.AltBehavior.OnceOnly + }) + behaviorMap.push_back({ + "key": "cycle:", + "value": AltBehavior.AltBehavior.Cycle + }) + behaviorMap.push_back({ + "key": "shuffle:", + "value": AltBehavior.AltBehavior.Shuffle + }) + var behavior2: AltBehavior.AltBehavior = AltBehavior.AltBehavior.Sequence - var tempIterator if true: - var this1: Variant = behaviorMap - tempIterator = this1.keys() - - while (tempIterator.get("hasNext").call()): - var prefix2: String = tempIterator.get("next").call() - if (StringTools.startsWith(expr, prefix2)): - expr = StringTools.trim(expr.substr(prefix2.length())) - var tempRight - if true: - var this1: Variant = behaviorMap - tempRight = this1.__get(prefix2) - behavior2 = tempRight - break + var _g: int = 0 + var _g1: Array[Variant] = behaviorMap + while (_g < _g1.size()): + var prefix: Variant = _g1[_g] + _g += 1 + if (StringTools.startsWith(expr, prefix.get("key"))): + expr = StringTools.trim(expr.substr(prefix.get("key").length())) + behavior2 = prefix.get("value") + break var outputsBuffer: HankBuffer = HankBuffer.Dummy(expr) var eachOutputExpr: Array[String] = outputsBuffer.rootSplit("|") @@ -58,25 +79,28 @@ static func parse(buffer: HankBuffer) -> Variant: if (eachOutputExpr.size() == 1): return { "_index": 1 } - var _g: Array[Output] = ([] as Array[Output]) - var _g1: int = 0 + var tempArray - while (_g1 < eachOutputExpr.size()): - var outputExpr: String = eachOutputExpr[_g1] - _g1 += 1 - var tempString1 + if true: + var _g: Array[Output] = ([] as Array[Output]) if true: - var l: int = outputExpr.length() - var r: int = 0 - while (r < l && StringTools.isSpace(outputExpr, r)): - r += 1 - if (r > 0): - tempString1 = outputExpr.substr(r, l - r) - else: - tempString1 = outputExpr - _g.push_back(Output.parse(HankBuffer.Dummy(tempString1), true)) + var _g1: int = 0 + while (_g1 < eachOutputExpr.size()): + var outputExpr: String = eachOutputExpr[_g1] + _g1 += 1 + var tempString1 + if true: + var l: int = outputExpr.length() + var r: int = 0 + while (r < l && StringTools.isSpace(outputExpr, r)): + r += 1 + if (r > 0): + tempString1 = outputExpr.substr(r, l - r) + else: + tempString1 = outputExpr + _g.push_back(Output.parse(HankBuffer.Dummy(tempString1), true)) + tempArray = _g buffer.take(rawExpr.length()) - return { "_index": 0, "v": Alt.new(behavior2, _g) } - + return { "_index": 0, "v": Alt.new(behavior2, tempArray) } diff --git a/hank-godot/scripts/CallStack.gd b/hank-godot/scripts/CallStack.gd deleted file mode 100644 index dcd06ec..0000000 --- a/hank-godot/scripts/CallStack.gd +++ /dev/null @@ -1,143 +0,0 @@ -class_name haxe__CallStack_CallStack_Impl_ - -func _init() -> void: - pass - -static func callStack() -> Array[Variant]: - return haxe_NativeStackTrace.toHaxe(haxe_NativeStackTrace.callStack()) - -static func exceptionStack(fullStack: bool = false) -> Array[Variant]: - var eStack: Array[Variant] = haxe_NativeStackTrace.toHaxe(haxe_NativeStackTrace.exceptionStack()) - var tempCallStack - - if (fullStack): - tempCallStack = eStack - else: - tempCallStack = haxe__CallStack_CallStack_Impl_.subtract(eStack, haxe__CallStack_CallStack_Impl_.callStack()) - - var this1: Array[Variant] = tempCallStack - - return this1 - -static func subtract(this1: Array[Variant], stack: Array[Variant]) -> Array[Variant]: - var startIndex: int = -1 - var i: int = -1 - - while true: - var tempLeft - i += 1 - tempLeft = i - if !(tempLeft < this1.size()): - break - var _g: int = 0 - var _g1: int = stack.size() - while (_g < _g1): - var tempNumber - _g += 1 - tempNumber = _g - 1 - var j: int = tempNumber - if (haxe__CallStack_CallStack_Impl_.equalItems(this1[i], stack[j])): - if (startIndex < 0): - startIndex = i - i += 1 - if (i >= this1.size()): - break - else: - startIndex = -1 - if (startIndex >= 0): - break - - var tempResult - - if (startIndex >= 0): - tempResult = this1.slice(0, startIndex) - else: - tempResult = this1 - - return tempResult - -static func equalItems(item1, item2) -> bool: - var tempResult - - if (item1 == null): - if (item2 == null): - tempResult = true - else: - tempResult = false - else: - match (item1._index): - 0: - if (item2 == null): - tempResult = false - else: - if (item2._index == 0): - tempResult = true - else: - tempResult = false - 1: - var _g: String = item1.m - if (item2 == null): - tempResult = false - else: - if (item2._index == 1): - var _g1: String = item2.m - var m2: String = _g1 - var m1: String = _g - tempResult = m1 == m2 - else: - tempResult = false - 2: - var _g = item1.s - var _g1: String = item1.file - var _g2: int = item1.line - var _g3 = item1.column - if (item2 == null): - tempResult = false - else: - if (item2._index == 2): - var _g4 = item2.s - var _g5: String = item2.file - var _g6: int = item2.line - var _g7 = item2.column - var item3 = _g4 - var file2: String = _g5 - var line2: int = _g6 - var col2 = _g7 - var col1 = _g3 - var line1: int = _g2 - var file1: String = _g1 - var item4 = _g - tempResult = file1 == file2 && line1 == line2 && col1 == col2 && haxe__CallStack_CallStack_Impl_.equalItems(item4, item3) - else: - tempResult = false - 3: - var _g = item1.classname - var _g1: String = item1.method - if (item2 == null): - tempResult = false - else: - if (item2._index == 3): - var _g2 = item2.classname - var _g3: String = item2.method - var class2 = _g2 - var method2: String = _g3 - var method1: String = _g1 - var class1 = _g - tempResult = class1 == class2 && method1 == method2 - else: - tempResult = false - 4: - var _g = item1.v - if (item2 == null): - tempResult = false - else: - if (item2._index == 4): - var _g1 = item2.v - var v2 = _g1 - var v1 = _g - tempResult = v1 == v2 - else: - tempResult = false - - return tempResult - diff --git a/hank-godot/scripts/HInterface.gd b/hank-godot/scripts/HInterface.gd index ca7adfb..a3c5e76 100644 --- a/hank-godot/scripts/HInterface.gd +++ b/hank-godot/scripts/HInterface.gd @@ -485,4 +485,3 @@ static func viewCountOf(viewCounts2: Variant, val) -> Variant: tempResult = { "_index": 1 } return tempResult - diff --git a/hank-godot/scripts/HParser.gd b/hank-godot/scripts/HParser.gd index ed28dcd..bbede98 100644 --- a/hank-godot/scripts/HParser.gd +++ b/hank-godot/scripts/HParser.gd @@ -8,54 +8,6 @@ var ast: Array[Variant] = [] func _init() -> void: choices = 0 - symbols.push_back({ - "key": "INCLUDE ", - "value": HParser.include - }) - symbols.push_back({ - "key": "<-", - "value": HParser.thread - }) - symbols.push_back({ - "key": "->", - "value": HParser.divert - }) - symbols.push_back({ - "key": "===", - "value": HParser.knot - }) - symbols.push_back({ - "key": "==", - "value": HParser.knot - }) - symbols.push_back({ - "key": "=", - "value": HParser.stitch - }) - symbols.push_back({ - "key": "~", - "value": HParser.haxeLine - }) - symbols.push_back({ - "key": "```", - "value": HParser.haxeBlock - }) - symbols.push_back({ - "key": "-", - "value": HParser.gather - }) - symbols.push_back({ - "key": "*", - "value": HParser.choice - }) - symbols.push_back({ - "key": "+", - "value": HParser.choice - }) - symbols.push_back({ - "key": "#", - "value": HParser.tag - }) func parseString(h: String) -> Array[Variant]: var stringBuffer: HankBuffer = HankBuffer.Dummy(h) @@ -148,6 +100,56 @@ func parseFile(f: String, files = null, includedFile: bool = false) -> Array[Var return parsedAST static func parseExpr(buffer: HankBuffer, position: Position) -> Variant: + if (symbols.size() == 0): + symbols.push_back({ + "key": "INCLUDE ", + "value": HParser.include + }) + symbols.push_back({ + "key": "<-", + "value": HParser.thread + }) + symbols.push_back({ + "key": "->", + "value": HParser.divert + }) + symbols.push_back({ + "key": "===", + "value": HParser.knot + }) + symbols.push_back({ + "key": "==", + "value": HParser.knot + }) + symbols.push_back({ + "key": "=", + "value": HParser.stitch + }) + symbols.push_back({ + "key": "~", + "value": HParser.haxeLine + }) + symbols.push_back({ + "key": "```", + "value": HParser.haxeBlock + }) + symbols.push_back({ + "key": "-", + "value": HParser.gather + }) + symbols.push_back({ + "key": "*", + "value": HParser.choice + }) + symbols.push_back({ + "key": "+", + "value": HParser.choice + }) + symbols.push_back({ + "key": "#", + "value": HParser.tag + }) + var line: Variant = buffer.peekLine("") match (line._index): @@ -311,7 +313,7 @@ static func tag(buffer: HankBuffer, position: Position) -> Variant: else: haxe_Log.trace.call("Warning! Can't apply tags to " + str(expr), { "fileName": "../hank/HParser.hx", - "lineNumber": 234, + "lineNumber": 236, "className": "hank.HParser", "methodName": "tag" }) diff --git a/hank-godot/scripts/HankAssert.gd b/hank-godot/scripts/HankAssert.gd deleted file mode 100644 index 548b73c..0000000 --- a/hank-godot/scripts/HankAssert.gd +++ /dev/null @@ -1,59 +0,0 @@ -class_name HankAssert - -func _init() -> void: - pass - -static func _assert(condition: bool, message: String) -> void: - if (condition): - HankAssert._pass(message) - else: - HankAssert.fail(message) - -static func _pass(message: String) -> void: - message = " ✓" + message - LogUtil.cleanTrace(message) - -static func fail(message: String) -> void: - message = " ✗" + message - LogUtil.cleanTrace(message) - -static func equals(expected, actual, pos = null) -> void: - var verboseMessage: String = " Asserting that " + str(actual) + " is expected value " + str(expected) - - HankAssert._assert(str(expected) == str(actual), verboseMessage) - -static func contains(expected: String, actual: String) -> void: - var verboseMessage: String = " Asserting that \"" + actual + "\" contains \"" + expected + "\"" - - HankAssert._assert(actual.find(expected) != -1, verboseMessage) - -static func notContains(unexpected: String, actual: String) -> void: - var verboseMessage: String = " Asserting that \"" + actual + "\" does not contain \"" + unexpected + "\"" - - HankAssert._assert(actual.find(unexpected) == -1, verboseMessage) - -static func isSome(option: Variant) -> void: - var verboseMessage: String = " Asserting that " + str(option) + " is Some(_)" - - match (option._index): - 0: - var _g = option.v - HankAssert._pass(verboseMessage) - 1: - HankAssert.fail(verboseMessage) - -static func isNone(option: Variant) -> void: - var verboseMessage: String = " Asserting that " + str(option) + " is None" - - match (option._index): - 0: - var _g = option.v - HankAssert.fail(verboseMessage) - 1: - HankAssert._pass(verboseMessage) - -static func throws(f, message = null) -> void: - if true: - f.call() - HankAssert.fail("Expected an exception to be thrown") - diff --git a/hank-godot/scripts/HankBuffer.gd b/hank-godot/scripts/HankBuffer.gd index 667111b..bb144b8 100644 --- a/hank-godot/scripts/HankBuffer.gd +++ b/hank-godot/scripts/HankBuffer.gd @@ -113,6 +113,8 @@ func rootIndexOf(s: String) -> int: return start start += 1 + return -1 + func rootSplit(delimiter: String) -> Array[String]: var rootIndices: Array[int] = self.everyRootIndexOf(delimiter) @@ -444,7 +446,8 @@ static func Dummy(text: String) -> HankBuffer: return HankBuffer.new("_", text, 1, 1) static func FromFile(path2: String, files = null) -> HankBuffer: - var rawBuffer2: String = File.getContent(path2) + var rawBuffer2: String = "" + + rawBuffer2 = FileAccess.open(path2, FileAccess.READ).get_as_text() return HankBuffer.new(path2, rawBuffer2) - diff --git a/hank-godot/scripts/Interp.gd b/hank-godot/scripts/Interp.gd index 0d2382d..cd0987f 100644 --- a/hank-godot/scripts/Interp.gd +++ b/hank-godot/scripts/Interp.gd @@ -141,7 +141,7 @@ func initOps() -> void: return v1 * v2) self.assignOp("/=", func(v1: float, v2: float) -> float: return v1 / v2) - self.assignOp("%=", func(v1: float, v2: float) -> float: + self.assignOp("%=", func(v1: int, v2: int) -> int: return v1 % v2) self.assignOp("&=", func(v1: int, v2: int) -> int: return v1 & v2) @@ -838,13 +838,10 @@ func expr(e: Variant): if (isAllString): tempVar1 = haxe_ds_StringMap.new() else: - if (isAllEnum): - tempVar1 = haxe_ds_EnumValueMap.new() + if (isAllObject): + tempVar1 = haxe_ds_ObjectMap.new() else: - if (isAllObject): - tempVar1 = haxe_ds_ObjectMap.new() - else: - assert(false, str("Inconsistent key types")) + assert(false, str("Inconsistent key types")) var map = tempVar1 if true: var _g2: int = 0 @@ -1091,10 +1088,4 @@ func _call(o, f, args: Array[Variant]): return f.callv(args) func cnew(cl: String, args: Array[Variant]): - var c: Class = Type.resolveClass(cl) - - if (c == null): - c = self.resolve(cl) - - return Type.createInstance(c, args) - + return null diff --git a/hank-godot/scripts/Output.gd b/hank-godot/scripts/Output.gd index 2d089b1..e4ac524 100644 --- a/hank-godot/scripts/Output.gd +++ b/hank-godot/scripts/Output.gd @@ -266,6 +266,8 @@ static func parseBraceExpression(buffer: HankBuffer) -> Variant: else: return Output.parseHaxeExpression(buffer) + return null + static func parseHaxeExpression(buffer: HankBuffer) -> Variant: var rawExpression: String = Extensions.unwrap(buffer.findNestedExpression("{", "}", 0, true)).checkValue() var hExpression: String = rawExpression.substr(1, rawExpression.length() - 2) @@ -285,4 +287,3 @@ static func appendNextText(story: Story, fullOutput: String, throwOnFail: String assert(false, str(throwOnFail)) return fullOutput - diff --git a/hank-godot/scripts/Parser.gd b/hank-godot/scripts/Parser.gd index 99763c8..4eaa04f 100644 --- a/hank-godot/scripts/Parser.gd +++ b/hank-godot/scripts/Parser.gd @@ -24,7 +24,7 @@ func _init() -> void: self.opChars = "+*/-=!><&|^%~" self.identChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_" - var priorities: Array[Array[String]] = ([(["%"] as Array[String]), (["*", "/"] as Array[String]), (["+", "-"] as Array[String]), (["<<", ">>", ">>>"] as Array[String]), (["|", "&", "^"] as Array[String]), (["==", "!=", ">", "<", ">=", "<="] as Array[String]), (["..."] as Array[String]), (["&&"] as Array[String]), (["||"] as Array[String]), (["=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "|=", "&=", "^=", "=>"] as Array[String]), (["->"] as Array[String])] as Array[Array[String]]) + var priorities: Array[Variant] = ([(["%"] as Array[Variant]), (["*", "/"] as Array[Variant]), (["+", "-"] as Array[Variant]), (["<<", ">>", ">>>"] as Array[Variant]), (["|", "&", "^"] as Array[Variant]), (["==", "!=", ">", "<", ">=", "<="] as Array[Variant]), (["..."] as Array[Variant]), (["&&"] as Array[Variant]), (["||"] as Array[Variant]), (["=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "|=", "&=", "^=", "=>"] as Array[Variant]), (["->"] as Array[Variant])] as Array[Variant]) self.opPriority = haxe_ds_StringMap.new() self.opRightAssoc = haxe_ds_StringMap.new() @@ -37,10 +37,10 @@ func _init() -> void: _g += 1 tempNumber = _g - 1 var i: int = tempNumber + var p: Array[Variant] = (priorities[i] as Array[Variant]) var _g2: int = 0 - var _g3: Array[String] = priorities[i] - while (_g2 < _g3.size()): - var x: String = _g3[_g2] + while (_g2 < p.size()): + var x = p[_g2] _g2 += 1 self.opPriority.__set(x, i) if (i == 9): @@ -173,13 +173,13 @@ func ensure(tk) -> void: if (t != tk): self.unexpected(t) -func ensureToken(tk) -> void: +func ensureToken(tk: Variant) -> void: var t = self.token() if (!Type.enumEq(t, tk)): self.unexpected(t) -func maybe(tk) -> bool: +func maybe(tk: Variant) -> bool: var t = self.token() if (Type.enumEq(t, tk)): @@ -196,7 +196,7 @@ func getIdent() -> String: if (tk == null): self.unexpected(tk) - return null + return "" else: if (tk._index == 2): var _g: String = tk.s @@ -204,7 +204,7 @@ func getIdent() -> String: return id else: self.unexpected(tk) - return null + return "" func expr(e: Variant) -> Variant: return e @@ -475,7 +475,7 @@ func parseExpr(): tk = self.token() if (tk == { "_index": 5 }): if true: - var tk2 = { "_index": 3, "s": "->" } + var tk2: Variant = { "_index": 3, "s": "->" } var t = self.token() if (!Type.enumEq(t, tk2)): self.unexpected(t) @@ -706,7 +706,7 @@ func parseLambda(args: Array[Variant], pmin) -> Variant: break if true: - var tk = { "_index": 3, "s": "->" } + var tk: Variant = { "_index": 3, "s": "->" } var t = self.token() if (!Type.enumEq(t, tk)): self.unexpected(t) @@ -937,7 +937,7 @@ func parseStructure(id: String): self.unexpected(t) var vname: String = self.getIdent() if true: - var tk = { "_index": 2, "s": "in" } + var tk: Variant = { "_index": 2, "s": "in" } var t = self.token() if (!Type.enumEq(t, tk)): self.unexpected(t) @@ -1180,7 +1180,7 @@ func parseStructure(id: String): "try": var e = self.parseExpr() if true: - var tk = { "_index": 2, "s": "catch" } + var tk: Variant = { "_index": 2, "s": "catch" } var t = self.token() if (!Type.enumEq(t, tk)): self.unexpected(t) @@ -1197,7 +1197,7 @@ func parseStructure(id: String): if (self.allowTypes): t = self.parseType() else: - var tk = { "_index": 2, "s": "Dynamic" } + var tk: Variant = { "_index": 2, "s": "Dynamic" } var t2 = self.token() if (!Type.enumEq(t2, tk)): self.unexpected(t2) @@ -1756,6 +1756,8 @@ func parseType() -> Variant: _: return self.unexpected(t) + return null + func parseTypeNext(t: Variant) -> Variant: var tk = self.token() @@ -1980,7 +1982,7 @@ func parseModuleDecl() -> Variant: var name: String = self.getIdent() var params: Variant = self.parseParams() if true: - var tk = { "_index": 3, "s": "=" } + var tk: Variant = { "_index": 3, "s": "=" } var t = self.token() if (!Type.enumEq(t, tk)): self.unexpected(t) @@ -2655,6 +2657,8 @@ func evalPreproCond(e: Variant) -> bool: assert(false, str(err)) return false + return false + func preprocess(id: String) -> Variant: match (id): "else", "elseif": @@ -2846,4 +2850,3 @@ func tokenString(t: Variant) -> String: tempResult = "#" + id return tempResult - diff --git a/hank-godot/scripts/Random.gd b/hank-godot/scripts/Random.gd index fd1a540..859f70d 100644 --- a/hank-godot/scripts/Random.gd +++ b/hank-godot/scripts/Random.gd @@ -405,4 +405,3 @@ static func bound(Value: float, Min = null, Max = null) -> float: tempResult = lowerBound return tempResult - diff --git a/hank-godot/scripts/Story.gd b/hank-godot/scripts/Story.gd index de18e99..c228762 100644 --- a/hank-godot/scripts/Story.gd +++ b/hank-godot/scripts/Story.gd @@ -776,4 +776,3 @@ static func FromFile(script: String, files = null, randomSeed = null) -> Story: var ast2: Array[Variant] = parser2.parseFile(script, files, false) return Story.FromAST(script, ast2, randomSeed) - diff --git a/hank-godot/scripts/StoryTestCase.gd b/hank-godot/scripts/StoryTestCase.gd deleted file mode 100644 index 273d939..0000000 --- a/hank-godot/scripts/StoryTestCase.gd +++ /dev/null @@ -1,142 +0,0 @@ -class_name StoryTestCase - -var testsDirectory: String -var files: Variant = haxe_ds_StringMap.new() -var lastTranscriptLine: int = 0 - -func _init(testsDirectory2: String, preloadedFiles = null) -> void: - self.testsDirectory = testsDirectory2 - - if (preloadedFiles != null): - self.files = preloadedFiles - -func testAllExamples() -> void: - var exampleTranscripts: Variant = haxe_ds_StringMap.new() - var exampleFolders: Array[String] = FileSystem.readDirectory(self.testsDirectory) - - if true: - var _g: int = 0 - while (_g < exampleFolders.size()): - var folder: String = exampleFolders[_g] - _g += 1 - var files2: Array[String] = FileSystem.readDirectory("" + self.testsDirectory + "/" + folder) - if true: - var tempArray - if true: - var _g2: Array[String] = ([] as Array[String]) - if true: - var _g1: int = 0 - while (_g1 < files2.size()): - var file: String = files2[_g1] - _g1 += 1 - if (StringTools.endsWith(file, ".hlog")): - _g2.push_back(file) - tempArray = _g2 - var value: Array[String] = tempArray - exampleTranscripts.__set(folder, value) - if true: - var folder: Variant = exampleTranscripts.keys() - while (folder.get("hasNext").call()): - var folder2: String = folder.get("next").call() - if (StringTools.startsWith(folder2, "_")): - LogUtil.cleanTrace(" Skipping tests for example \"" + folder2 + "\"") - continue - LogUtil.cleanTrace(" Running tests for example \"" + folder2 + "\"") - if true: - var _g: int = 0 - var _g1 = exampleTranscripts.__get(folder2) - while (_g < _g1.size()): - var file: String = _g1[_g] - _g += 1 - var disabled: bool = file.find("disabled") != -1 - var debug: bool = file.find("debug") != -1 - var partial: bool = file.find("partial") != -1 - if (!disabled): - LogUtil.cleanTrace(" Running " + file) - self.validateAgainstTranscript("" + self.testsDirectory + "/" + folder2 + "/main.hank", "" + self.testsDirectory + "/" + folder2 + "/" + file, !partial, debug) - -func validateAgainstTranscript(storyFile: String, transcriptFile: String, fullTranscript: bool = true, debug: bool = false) -> void: - var buffer: HankBuffer = HankBuffer.FromFile(transcriptFile, self.files) - var transcriptLines: Array[String] = buffer.lines() - var randomSeed = null - - if (StringTools.startsWith(transcriptLines[0], "@")): - randomSeed = transcriptLines[0].substr(1).to_int() - if true: - var index: int = transcriptLines.find(transcriptLines[0]) - if (index >= 0): - transcriptLines.remove_at(index) - true - else: - false - - var story: Story = null - - story = Story.FromFile(storyFile, self.files, randomSeed) - - story.hInterface.addVariable("DEBUG", debug) - - var i: int = 0 - - while (i < transcriptLines.size()): - var line: String = transcriptLines[i] - self.lastTranscriptLine = i - if (StringTools.startsWith(line, "#")): - var tempArray - var _this: String = StringTools.trim(line.substr(1)) - tempArray = Array(Array(_this.split(":")), Variant.Type.TYPE_STRING, "", null) - var parts: Array[String] = tempArray - HankAssert.equals(StringTools.trim(parts[1]), story.hInterface.evaluateExpr(parts[0], story.nodeScopes), null) - i += 1 - continue - var frame: Variant = story.nextFrame() - if (StringTools.startsWith(line, "*")): - var choices: Array[String] = [] - while true: - choices.push_back(line.substr(2)) - var tempIndex - i += 1 - tempIndex = i - line = transcriptLines[tempIndex] - if !(line != null && StringTools.startsWith(line, "*")): - break - var tempArray1 - var _g: Array[Variant] = ([] as Array[Variant]) - var _g1: int = 0 - while (_g1 < choices.size()): - var choice: String = choices[_g1] - _g1 += 1 - _g.push_back(([] as Array[Variant])) - tempArray1 = _g - HankAssert.equals({ "_index": 1, "choices": choices, "tags": tempArray1 }, frame, null) - continue - else: - if (StringTools.startsWith(line, ">")): - var tempRight - var s: String = line.substr(1) - var l: int = s.length() - var r: int = 0 - while (r < l && StringTools.isSpace(s, r)): - r += 1 - if (r > 0): - tempRight = s.substr(r, l - r) - else: - tempRight = s - line = tempRight - var firstColonIdx: int = line.find(":") - if (firstColonIdx == -1): - assert(false, str("Choice line in transcript does not specify expected output: > " + line)) - var index: int = line.substr(0, firstColonIdx).to_int() - 1 - var expectedOutput: String = StringTools.trim(line.substr(firstColonIdx + 1)) - var output: String = story.choose(index) - HankAssert.equals(expectedOutput, output, null) - else: - if (line.length() > 0): - HankAssert.equals({ "_index": 0, "text": line }, frame, null) - if (frame == { "_index": 2 }): - break - i += 1 - - if (fullTranscript): - HankAssert.equals({ "_index": 2 }, story.nextFrame(), null) - diff --git a/hank-godot/scripts/Type.gd b/hank-godot/scripts/Type.gd new file mode 100644 index 0000000..26c55ad --- /dev/null +++ b/hank-godot/scripts/Type.gd @@ -0,0 +1,10 @@ +class_name Type + +func _init() -> void: + pass + +static func enumEq(a, b) -> bool: + return false + +static func enumConstructor(e) -> String: + return "" diff --git a/hank-godot/scripts/_GeneratedFiles.json b/hank-godot/scripts/_GeneratedFiles.json index c23731c..0b986d8 100644 --- a/hank-godot/scripts/_GeneratedFiles.json +++ b/hank-godot/scripts/_GeneratedFiles.json @@ -1,13 +1,9 @@ { "filesGenerated": [ - "tests_Examples.gd", - "sys_FileSeek.gd", "plugin.gd", "plugin.cfg", - "hscript_Interp_Stop.gd", "hscript_Expr_FieldAccess.gd", "haxe_ds_GenericStack_GenericCell.gd", - "haxe_ds_BalancedTree_TreeNode.gd", "haxe_StringMap.gd", "haxe_StringKeyValueIterator.gd", "haxe_StringIterator.gd", @@ -17,12 +13,12 @@ "haxe_MapKeyValueIterator.gd", "haxe_IntMap.gd", "haxe_GenericStack.gd", - "haxe_EnumValueMap.gd", "haxe_Encoding.gd", - "haxe_BalancedTree.gd", "haxe_ArrayIterator.gd", "hank_Story_EmbedMode.gd", "hank_StoryTree_StoryNode.gd", + "hank_Parser_Type.gd", + "hank_Interp_Stop.gd", "hank_HankBuffer_Position.gd", "hank_HankBuffer_BufferSlice.gd", "hank_HankAST_ASTExtension.gd", @@ -34,7 +30,6 @@ "SysTools.gd", "StringTools.gd", "StringBuf.gd", - "StoryTestCase.gd", "Story.gd", "Reflect.gd", "Random.gd", @@ -45,16 +40,14 @@ "Interp.gd", "IntIterator.gd", "HankBuffer.gd", - "HankAssert.gd", "HParser.gd", "HInterface.gd", "Extensions.gd", "Exception.gd", "EReg.gd", - "CallStack.gd", "Alt.gd" ], - "id": 33, + "id": 62, "wasCached": false, "version": 1 } \ No newline at end of file diff --git a/hank-godot/scripts/hank_HInterface_HankInterp.gd b/hank-godot/scripts/hank_HInterface_HankInterp.gd index 81405fd..b003932 100644 --- a/hank-godot/scripts/hank_HInterface_HankInterp.gd +++ b/hank-godot/scripts/hank_HInterface_HankInterp.gd @@ -7,6 +7,7 @@ var story: Story func _init(hInterface2: HInterface) -> void: self.hInterface = hInterface2 + super() func setStory(story2: Story) -> void: self.story = story2 @@ -115,4 +116,3 @@ func assign(e1: Variant, e2: Variant): return super.assign(e1, e2) else: return super.assign(e1, e2) - diff --git a/hank-godot/scripts/hscript_Interp_Stop.gd b/hank-godot/scripts/hank_Interp_Stop.gd similarity index 100% rename from hank-godot/scripts/hscript_Interp_Stop.gd rename to hank-godot/scripts/hank_Interp_Stop.gd diff --git a/hank-godot/scripts/hank_Parser_Type.gd b/hank-godot/scripts/hank_Parser_Type.gd new file mode 100644 index 0000000..7e4a2e5 --- /dev/null +++ b/hank-godot/scripts/hank_Parser_Type.gd @@ -0,0 +1,11 @@ +class_name Type + +func _init() -> void: + pass + +static func enumEq(a, b) -> bool: + return false + +static func enumConstructor(e) -> String: + return "" + diff --git a/hank-godot/scripts/haxe_BalancedTree.gd b/hank-godot/scripts/haxe_BalancedTree.gd deleted file mode 100644 index 7b599bd..0000000 --- a/hank-godot/scripts/haxe_BalancedTree.gd +++ /dev/null @@ -1,132 +0,0 @@ -class_name haxe_ds_BalancedTree - -var root: haxe_ds_TreeNode - -func _init() -> void: - pass - -func __set(key, value) -> void: - self.root = self.setLoop(key, value, self.root) - -func __get(key): - var node: haxe_ds_TreeNode = self.root - - while (node != null): - var c: int = self.compare(key, node.key) - if (c == 0): - return node.value - if (c < 0): - node = node.left - else: - node = node.right - - return null - -func keys() -> Variant: - var ret: Array[Variant] = ([] as Array[Variant]) - - self.keysLoop(self.root, ret) - - return haxe_iterators_ArrayIterator.new(ret) - -func setLoop(k, v, node: haxe_ds_TreeNode) -> haxe_ds_TreeNode: - if (node == null): - return haxe_ds_TreeNode.new(null, k, v, null) - - var c: int = self.compare(k, node.key) - var tempResult - - if (c == 0): - var tempNumber - if (node == null): - tempNumber = 0 - else: - tempNumber = node._height - tempResult = haxe_ds_TreeNode.new(node.left, k, v, node.right, tempNumber) - else: - if (c < 0): - var nl: haxe_ds_TreeNode = self.setLoop(k, v, node.left) - tempResult = self.balance(nl, node.key, node.value, node.right) - else: - var nr: haxe_ds_TreeNode = self.setLoop(k, v, node.right) - tempResult = self.balance(node.left, node.key, node.value, nr) - - return tempResult - -func keysLoop(node: haxe_ds_TreeNode, acc: Array[Variant]) -> void: - if (node != null): - self.keysLoop(node.left, acc) - acc.push_back(node.key) - self.keysLoop(node.right, acc) - -func balance(l: haxe_ds_TreeNode, k, v, r: haxe_ds_TreeNode) -> haxe_ds_TreeNode: - var tempNumber - - if (l == null): - tempNumber = 0 - else: - tempNumber = l._height - - var hl: int = tempNumber - var tempNumber1 - - if (r == null): - tempNumber1 = 0 - else: - tempNumber1 = r._height - - var hr: int = tempNumber1 - var tempResult - - if (hl > hr + 2): - var tempLeft - if true: - var _this: haxe_ds_TreeNode = l.left - if (_this == null): - tempLeft = 0 - else: - tempLeft = _this._height - var tempRight - if true: - var _this: haxe_ds_TreeNode = l.right - if (_this == null): - tempRight = 0 - else: - tempRight = _this._height - if (tempLeft >= tempRight): - tempResult = haxe_ds_TreeNode.new(l.left, l.key, l.value, haxe_ds_TreeNode.new(l.right, k, v, r)) - else: - tempResult = haxe_ds_TreeNode.new(haxe_ds_TreeNode.new(l.left, l.key, l.value, l.right.left), l.right.key, l.right.value, haxe_ds_TreeNode.new(l.right.right, k, v, r)) - else: - if (hr > hl + 2): - var tempLeft1 - if true: - var _this: haxe_ds_TreeNode = r.right - if (_this == null): - tempLeft1 = 0 - else: - tempLeft1 = _this._height - var tempRight1 - if true: - var _this: haxe_ds_TreeNode = r.left - if (_this == null): - tempRight1 = 0 - else: - tempRight1 = _this._height - if (tempLeft1 > tempRight1): - tempResult = haxe_ds_TreeNode.new(haxe_ds_TreeNode.new(l, k, v, r.left), r.key, r.value, r.right) - else: - tempResult = haxe_ds_TreeNode.new(haxe_ds_TreeNode.new(l, k, v, r.left.left), r.left.key, r.left.value, haxe_ds_TreeNode.new(r.left.right, r.key, r.value, r.right)) - else: - var tempNumber2 - if (hl > hr): - tempNumber2 = hl - else: - tempNumber2 = hr - tempResult = haxe_ds_TreeNode.new(l, k, v, r, (tempNumber2) + 1) - - return tempResult - -func compare(k1, k2) -> int: - return Reflect.compare(k1, k2) - diff --git a/hank-godot/scripts/haxe_EnumValueMap.gd b/hank-godot/scripts/haxe_EnumValueMap.gd index 7582f97..74f416b 100644 --- a/hank-godot/scripts/haxe_EnumValueMap.gd +++ b/hank-godot/scripts/haxe_EnumValueMap.gd @@ -50,4 +50,3 @@ func compareArg(v1, v2) -> int: tempResult = Reflect.compare(v1, v2) return tempResult - diff --git a/hank-godot/scripts/haxe_ds_BalancedTree_TreeNode.gd b/hank-godot/scripts/haxe_ds_BalancedTree_TreeNode.gd deleted file mode 100644 index 658faf5..0000000 --- a/hank-godot/scripts/haxe_ds_BalancedTree_TreeNode.gd +++ /dev/null @@ -1,46 +0,0 @@ -class_name haxe_ds_TreeNode - -var left: haxe_ds_TreeNode -var right: haxe_ds_TreeNode -var key -var value -var _height: int - -func _init(l: haxe_ds_TreeNode, k, v, r: haxe_ds_TreeNode, h: int = -1) -> void: - self.left = l - self.key = k - self.value = v - self.right = r - - if (h == -1): - var tempNumber - var tempLeft - if true: - var _this: haxe_ds_TreeNode = self.left - if (_this == null): - tempLeft = 0 - else: - tempLeft = _this._height - var tempRight - if true: - var _this: haxe_ds_TreeNode = self.right - if (_this == null): - tempRight = 0 - else: - tempRight = _this._height - if (tempLeft > tempRight): - var _this: haxe_ds_TreeNode = self.left - if (_this == null): - tempNumber = 0 - else: - tempNumber = _this._height - else: - var _this: haxe_ds_TreeNode = self.right - if (_this == null): - tempNumber = 0 - else: - tempNumber = _this._height - self._height = (tempNumber) + 1 - else: - self._height = h - diff --git a/hank-godot/scripts/sys_FileSeek.gd b/hank-godot/scripts/sys_FileSeek.gd deleted file mode 100644 index fe68752..0000000 --- a/hank-godot/scripts/sys_FileSeek.gd +++ /dev/null @@ -1,7 +0,0 @@ -class_name FileSeek extends Object - -enum FileSeek { - SeekBegin, - SeekCur, - SeekEnd, -} diff --git a/hank-godot/scripts/tests_Examples.gd b/hank-godot/scripts/tests_Examples.gd deleted file mode 100644 index c406854..0000000 --- a/hank-godot/scripts/tests_Examples.gd +++ /dev/null @@ -1,18 +0,0 @@ -class_name Examples - -static var files: Variant = haxe_ds_StringMap.new() -static var fileBuffer = func(path: String) -> HankBuffer: - return HankBuffer.new(path, files.__get(path)) - -func _init() -> void: - pass - -static func main() -> void: - haxe_Log.trace.call("Testing examples for target " + LogUtil.currentTarget(), { - "fileName": "../tests/main/Examples.hx", - "lineNumber": 8, - "className": "tests.main.Examples", - "methodName": "main" - }) - StoryTestCase.new("examples").testAllExamples() - diff --git a/hank/Alt.hx b/hank/Alt.hx index e785f59..f8830ee 100644 --- a/hank/Alt.hx +++ b/hank/Alt.hx @@ -19,16 +19,7 @@ class Alt { var behavior:AltBehavior; var outputs:Array; - static var behaviorMap = [ - '>' => Sequence, - '!' => OnceOnly, - '&' => Cycle, - '%' => Shuffle, - 'sequence:' => Sequence, - 'once:' => OnceOnly, - 'cycle:' => Cycle, - 'shuffle:' => Shuffle - ]; + static var behaviorMap:Array<{key:String, value:AltBehavior}> = []; public function new(behavior:AltBehavior, outputs:Array) { this.behavior = behavior; @@ -41,12 +32,23 @@ class Alt { var expr = rawExpr.substr(1, rawExpr.length - 2).ltrim(); // trace (expr); + if(behaviorMap.length == 0) { + behaviorMap.push({key: '>', value: Sequence}); + behaviorMap.push({key: '!', value: OnceOnly}); + behaviorMap.push({key: '&', value: Cycle}); + behaviorMap.push({key: '%', value: Shuffle}); + behaviorMap.push({key: 'sequence:', value: Sequence}); + behaviorMap.push({key: 'once:', value: OnceOnly}); + behaviorMap.push({key: 'cycle:', value: Cycle}); + behaviorMap.push({key: 'shuffle:', value: Shuffle}); + } + // Sequences are the default behavior var behavior = Sequence; - for (prefix in behaviorMap.keys()) { - if (expr.startsWith(prefix)) { - expr = expr.substr(prefix.length).trim(); - behavior = behaviorMap[prefix]; + for (prefix in behaviorMap) { + if (expr.startsWith(prefix.key)) { + expr = expr.substr(prefix.key.length).trim(); + behavior = prefix.value; break; // <-- Finally figured that one out. } } diff --git a/hank/HInterface.hx b/hank/HInterface.hx index 1fe3fe6..597efde 100644 --- a/hank/HInterface.hx +++ b/hank/HInterface.hx @@ -4,8 +4,8 @@ using Reflect; using Type; import haxe.ds.Option; -import hscript.Parser; -import hscript.Interp; +import hank.Parser; +import hank.Interp; import hscript.Expr; using hank.Extensions; @@ -24,6 +24,9 @@ class HankInterp extends Interp { public function new(hInterface:HInterface) { this.hInterface = hInterface; super(); + #if gdscript + untyped __gdscript__("super()"); + #end } public override function expr(e:Expr):Dynamic { diff --git a/hank/HParser.hx b/hank/HParser.hx index 08a8cba..55a49a7 100644 --- a/hank/HParser.hx +++ b/hank/HParser.hx @@ -20,18 +20,6 @@ class HParser { public function new() { choices = 0; - symbols.push({key: 'INCLUDE ', value: include}); - symbols.push({key: '<-', value: thread}); - symbols.push({key: '->', value: divert}); - symbols.push({key: '===', value: knot}); - symbols.push({key: '==', value: knot}); - symbols.push({key: '=', value: stitch}); - symbols.push({key: '~', value: haxeLine}); - symbols.push({key: '```', value: haxeBlock}); - symbols.push({key: '-', value: gather}); - symbols.push({key: '*', value: choice}); - symbols.push({key: '+', value: choice}); - symbols.push({key: '#', value: tag}); } public function parseString(h:String):HankAST { @@ -102,6 +90,20 @@ class HParser { } static function parseExpr(buffer:HankBuffer, position:HankBuffer.Position):ExprType { + if (symbols.length == 0) { + symbols.push({key: 'INCLUDE ', value: include}); + symbols.push({key: '<-', value: thread}); + symbols.push({key: '->', value: divert}); + symbols.push({key: '===', value: knot}); + symbols.push({key: '==', value: knot}); + symbols.push({key: '=', value: stitch}); + symbols.push({key: '~', value: haxeLine}); + symbols.push({key: '```', value: haxeBlock}); + symbols.push({key: '-', value: gather}); + symbols.push({key: '*', value: choice}); + symbols.push({key: '+', value: choice}); + symbols.push({key: '#', value: tag}); + }; var line = buffer.peekLine(); switch (line) { case None: diff --git a/hank/HankBuffer.hx b/hank/HankBuffer.hx index 0bf047b..6508962 100644 --- a/hank/HankBuffer.hx +++ b/hank/HankBuffer.hx @@ -88,15 +88,18 @@ class HankBuffer { public static function FromFile(path:String, ?files:PreloadedFiles) { // Keep a raw buffer of the file for tracking accurate file positions - #if (sys || hxnodejs) - var rawBuffer = sys.io.File.getContent(path); + var rawBuffer:String = ""; + #if gdscript + untyped __gdscript__('{0} = FileAccess.open({1}, FileAccess.READ).get_as_text()', rawBuffer, path); + #elseif (sys || hxnodejs) + rawBuffer = sys.io.File.getContent(path); #else if (files == null) { throw 'Tried to open file $path on a non-sys platform without passing in preloaded files'; } else if (!files.exists(path)) { throw 'Tried to open file $path that was not pre-loaded'; } - var rawBuffer = files[path]; + rawBuffer = files[path]; #end return new HankBuffer(path, rawBuffer); } @@ -148,6 +151,7 @@ class HankBuffer { } start += 1; } + return -1; } public function rootSplit(delimiter:String):Array { diff --git a/hank/Interp.hx b/hank/Interp.hx new file mode 100644 index 0000000..5ce67e2 --- /dev/null +++ b/hank/Interp.hx @@ -0,0 +1,707 @@ +/* + * Copyright (C)2008-2017 Haxe Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +package hank; +import haxe.PosInfos; +import hscript.Expr; +import haxe.Constraints.IMap; +import hscript.Tools; + +private enum Stop { + SBreak; + SContinue; + SReturn; +} + +class Interp { + + #if haxe3 + public var variables : Map; + var locals : Map; + var binops : Map Expr -> Dynamic >; + #else + public var variables : Hash; + var locals : Hash<{ r : Dynamic }>; + var binops : Hash< Expr -> Expr -> Dynamic >; + #end + + var depth : Int; + var inTry : Bool; + var declared : Array<{ n : String, old : { r : Dynamic } }>; + var returnValue : Dynamic; + + #if hscriptPos + var curExpr : Expr; + #end + + public function new() { + #if haxe3 + locals = new Map(); + #else + locals = new Hash(); + #end + declared = new Array(); + resetVariables(); + initOps(); + } + + private function resetVariables(){ + #if haxe3 + variables = new Map(); + #else + variables = new Hash(); + #end + + variables.set("null",null); + variables.set("true",true); + variables.set("false",false); + variables.set("trace", Reflect.makeVarArgs(function(el) { + var inf = posInfos(); + var v = el.shift(); + if( el.length > 0 ) inf.customParams = el; + haxe.Log.trace(Std.string(v), inf); + })); + } + + public function posInfos(): PosInfos { + #if hscriptPos + if (curExpr != null) + return cast { fileName : curExpr.origin, lineNumber : curExpr.line }; + #end + return cast { fileName : "hscript", lineNumber : 0 }; + } + + function initOps() { + var me = this; + #if haxe3 + binops = new Map(); + #else + binops = new Hash(); + #end + binops.set("+",function(e1,e2) return me.expr(e1) + me.expr(e2)); + binops.set("-",function(e1,e2) return me.expr(e1) - me.expr(e2)); + binops.set("*",function(e1,e2) return me.expr(e1) * me.expr(e2)); + binops.set("/",function(e1,e2) return me.expr(e1) / me.expr(e2)); + binops.set("%",function(e1,e2) return me.expr(e1) % me.expr(e2)); + binops.set("&",function(e1,e2) return me.expr(e1) & me.expr(e2)); + binops.set("|",function(e1,e2) return me.expr(e1) | me.expr(e2)); + binops.set("^",function(e1,e2) return me.expr(e1) ^ me.expr(e2)); + binops.set("<<",function(e1,e2) return me.expr(e1) << me.expr(e2)); + binops.set(">>",function(e1,e2) return me.expr(e1) >> me.expr(e2)); + binops.set(">>>",function(e1,e2) return me.expr(e1) >>> me.expr(e2)); + binops.set("==",function(e1,e2) return me.expr(e1) == me.expr(e2)); + binops.set("!=",function(e1,e2) return me.expr(e1) != me.expr(e2)); + binops.set(">=",function(e1,e2) return me.expr(e1) >= me.expr(e2)); + binops.set("<=",function(e1,e2) return me.expr(e1) <= me.expr(e2)); + binops.set(">",function(e1,e2) return me.expr(e1) > me.expr(e2)); + binops.set("<",function(e1,e2) return me.expr(e1) < me.expr(e2)); + binops.set("||",function(e1,e2) return me.expr(e1) == true || me.expr(e2) == true); + binops.set("&&",function(e1,e2) return me.expr(e1) == true && me.expr(e2) == true); + binops.set("=",assign); + binops.set("...",function(e1,e2) return new #if (haxe_211 || haxe3) IntIterator #else IntIter #end(me.expr(e1),me.expr(e2))); + assignOp("+=",function(v1:Dynamic,v2:Dynamic) return v1 + v2); + assignOp("-=",function(v1:Float,v2:Float) return v1 - v2); + assignOp("*=",function(v1:Float,v2:Float) return v1 * v2); + assignOp("/=",function(v1:Float,v2:Float) return v1 / v2); + assignOp("%=",function(v1:Int,v2:Int) return v1 % v2); + assignOp("&=",function(v1,v2) return v1 & v2); + assignOp("|=",function(v1,v2) return v1 | v2); + assignOp("^=",function(v1,v2) return v1 ^ v2); + assignOp("<<=",function(v1,v2) return v1 << v2); + assignOp(">>=",function(v1,v2) return v1 >> v2); + assignOp(">>>=",function(v1,v2) return v1 >>> v2); + } + + function setVar( name : String, v : Dynamic ) { + variables.set(name, v); + } + + function assign( e1 : Expr, e2 : Expr ) : Dynamic { + var v = expr(e2); + switch( Tools.expr(e1) ) { + case EIdent(id): + var l = locals.get(id); + if( l == null ) + setVar(id,v) + else + l.r = v; + case EField(e,f): + v = set(expr(e),f,v); + case EArray(e, index): + var arr:Dynamic = expr(e); + var index:Dynamic = expr(index); + if (isMap(arr)) { + setMapValue(arr, index, v); + } + else { + arr[index] = v; + } + + default: + error(EInvalidOp("=")); + } + return v; + } + + function assignOp( op, fop : Dynamic -> Dynamic -> Dynamic ) { + var me = this; + binops.set(op,function(e1,e2) return me.evalAssignOp(op,fop,e1,e2)); + } + + function evalAssignOp(op,fop,e1,e2) : Dynamic { + var v; + switch( Tools.expr(e1) ) { + case EIdent(id): + var l = locals.get(id); + v = fop(expr(e1),expr(e2)); + if( l == null ) + setVar(id,v) + else + l.r = v; + case EField(e,f): + var obj = expr(e); + v = fop(get(obj,f),expr(e2)); + v = set(obj,f,v); + case EArray(e, index): + var arr:Dynamic = expr(e); + var index:Dynamic = expr(index); + if (isMap(arr)) { + v = fop(getMapValue(arr, index), expr(e2)); + setMapValue(arr, index, v); + } + else { + v = fop(arr[index],expr(e2)); + arr[index] = v; + } + default: + return error(EInvalidOp(op)); + } + return v; + } + + function increment( e : Expr, prefix : Bool, delta : Int ) : Dynamic { + #if hscriptPos + curExpr = e; + var e = e.e; + #end + switch(e) { + case EIdent(id): + var l = locals.get(id); + var v : Dynamic = (l == null) ? resolve(id) : l.r; + if( prefix ) { + v += delta; + if( l == null ) setVar(id,v) else l.r = v; + } else + if( l == null ) setVar(id,v + delta) else l.r = v + delta; + return v; + case EField(e,f): + var obj = expr(e); + var v : Dynamic = get(obj,f); + if( prefix ) { + v += delta; + set(obj,f,v); + } else + set(obj,f,v + delta); + return v; + case EArray(e, index): + var arr:Dynamic = expr(e); + var index:Dynamic = expr(index); + if (isMap(arr)) { + var v = getMapValue(arr, index); + if (prefix) { + v += delta; + setMapValue(arr, index, v); + } + else { + setMapValue(arr, index, v + delta); + } + return v; + } + else { + var v = arr[index]; + if( prefix ) { + v += delta; + arr[index] = v; + } else + arr[index] = v + delta; + return v; + } + default: + return error(EInvalidOp((delta > 0)?"++":"--")); + } + } + + public function execute( expr : Expr ) : Dynamic { + depth = 0; + #if haxe3 + locals = new Map(); + #else + locals = new Hash(); + #end + declared = new Array(); + return exprReturn(expr); + } + + function exprReturn(e) : Dynamic { + try { + return expr(e); + } catch( e : Stop ) { + switch( e ) { + case SBreak: throw "Invalid break"; + case SContinue: throw "Invalid continue"; + case SReturn: + var v = returnValue; + returnValue = null; + return v; + } + } + return null; + } + + function duplicate( h : #if haxe3 Map < String, T > #else Hash #end ) { + #if haxe3 + var h2 = new Map(); + #else + var h2 = new Hash(); + #end + for( k in h.keys() ) + h2.set(k,h.get(k)); + return h2; + } + + function restore( old : Int ) { + while( declared.length > old ) { + var d = declared.pop(); + locals.set(d.n,d.old); + } + } + + inline function error(e : #if hscriptPos ErrorDef #else Error #end, rethrow=false ) : Dynamic { + #if hscriptPos var e = new Error(e, curExpr.pmin, curExpr.pmax, curExpr.origin, curExpr.line); #end + if( rethrow ) this.rethrow(e) else throw e; + return null; + } + + inline function rethrow( e : Dynamic ) { + #if hl + hl.Api.rethrow(e); + #else + throw e; + #end + } + + function resolve( id : String ) : Dynamic { + var l = locals.get(id); + if( l != null ) + return l.r; + var v = variables.get(id); + if( v == null && !variables.exists(id) ) + error(EUnknownVariable(id)); + return v; + } + + public function expr( e : Expr ) : Dynamic { + #if hscriptPos + curExpr = e; + var e = e.e; + #end + switch( e ) { + case EConst(c): + switch( c ) { + case CInt(v): return v; + case CFloat(f): return f; + case CString(s): return s; + #if !haxe3 + case CInt32(v): return v; + #end + } + case EIdent(id): + return resolve(id); + case EVar(n,_,e): + declared.push({ n : n, old : locals.get(n) }); + locals.set(n,{ r : (e == null)?null:expr(e) }); + return null; + case EParent(e): + return expr(e); + case EBlock(exprs): + var old = declared.length; + var v = null; + for( e in exprs ) + v = expr(e); + restore(old); + return v; + case EField(e,f): + return get(expr(e),f); + case EBinop(op,e1,e2): + var fop = binops.get(op); + if( fop == null ) error(EInvalidOp(op)); + return fop(e1,e2); + case EUnop(op,prefix,e): + switch(op) { + case "!": + return expr(e) != true; + case "-": + return -expr(e); + case "++": + return increment(e,prefix,1); + case "--": + return increment(e,prefix,-1); + case "~": + #if (neko && !haxe3) + return haxe.Int32.complement(expr(e)); + #else + return ~expr(e); + #end + default: + error(EInvalidOp(op)); + } + case ECall(e,params): + var args = new Array(); + for( p in params ) + args.push(expr(p)); + + switch( Tools.expr(e) ) { + case EField(e,f): + var obj = expr(e); + if( obj == null ) error(EInvalidAccess(f)); + return fcall(obj,f,args); + default: + return call(null,expr(e),args); + } + case EIf(econd,e1,e2): + return if( expr(econd) == true ) expr(e1) else if( e2 == null ) null else expr(e2); + case EWhile(econd,e): + whileLoop(econd,e); + return null; + case EDoWhile(econd,e): + doWhileLoop(econd,e); + return null; + case EFor(v,it,e): + forLoop(v,it,e); + return null; + case EBreak: + throw SBreak; + case EContinue: + throw SContinue; + case EReturn(e): + returnValue = e == null ? null : expr(e); + throw SReturn; + case EFunction(params,fexpr,name,_): + var capturedLocals = duplicate(locals); + var me = this; + var hasOpt = false, minParams = 0; + for( p in params ) + if( p.opt ) + hasOpt = true; + else + minParams++; + var f = function(args:Array) { + if( ( (args == null) ? 0 : args.length ) != params.length ) { + if( args.length < minParams ) { + var str = "Invalid number of parameters. Got " + args.length + ", required " + minParams; + if( name != null ) str += " for function '" + name+"'"; + error(ECustom(str)); + } + // make sure mandatory args are forced + var args2 = []; + var extraParams = args.length - minParams; + var pos = 0; + for( p in params ) + if( p.opt ) { + if( extraParams > 0 ) { + args2.push(args[pos++]); + extraParams--; + } else + args2.push(null); + } else + args2.push(args[pos++]); + args = args2; + } + var old = me.locals, depth = me.depth; + me.depth++; + me.locals = me.duplicate(capturedLocals); + for( i in 0...params.length ) + me.locals.set(params[i].name,{ r : args[i] }); + var r = null; + var oldDecl = declared.length; + if( inTry ) + try { + r = me.exprReturn(fexpr); + } catch( e : Dynamic ) { + me.locals = old; + me.depth = depth; + #if neko + neko.Lib.rethrow(e); + #else + throw e; + #end + } + else + r = me.exprReturn(fexpr); + restore(oldDecl); + me.locals = old; + me.depth = depth; + return r; + }; + var f = Reflect.makeVarArgs(f); + if( name != null ) { + if( depth == 0 ) { + // global function + variables.set(name, f); + } else { + // function-in-function is a local function + declared.push( { n : name, old : locals.get(name) } ); + var ref = { r : f }; + locals.set(name, ref); + capturedLocals.set(name, ref); // allow self-recursion + } + } + return f; + case EArrayDecl(arr): + if (arr.length > 0 && Tools.expr(arr[0]).match(EBinop("=>", _))) { + var isAllString:Bool = true; + var isAllInt:Bool = true; + var isAllObject:Bool = true; + var isAllEnum:Bool = true; + var keys:Array = []; + var values:Array = []; + for (e in arr) { + switch(Tools.expr(e)) { + case EBinop("=>", eKey, eValue): { + var key:Dynamic = expr(eKey); + var value:Dynamic = expr(eValue); + isAllString = isAllString && (key is String); + isAllInt = isAllInt && (key is Int); + isAllObject = isAllObject && Reflect.isObject(key); + isAllEnum = isAllEnum && Reflect.isEnumValue(key); + keys.push(key); + values.push(value); + } + default: throw("=> expected"); + } + } + var map:Dynamic = { + if (isAllInt) new haxe.ds.IntMap(); + else if (isAllString) new haxe.ds.StringMap(); + #if !gdscript + else if (isAllEnum) new haxe.ds.EnumValueMap(); + #end + else if (isAllObject) new haxe.ds.ObjectMap(); + else throw 'Inconsistent key types'; + } + for (n in 0...keys.length) { + setMapValue(map, keys[n], values[n]); + } + return map; + } + else { + var a = new Array(); + for ( e in arr ) { + a.push(expr(e)); + } + return a; + } + case EArray(e, index): + var arr:Dynamic = expr(e); + var index:Dynamic = expr(index); + if (isMap(arr)) { + return getMapValue(arr, index); + } + else { + return arr[index]; + } + case ENew(cl,params): + var a = new Array(); + for( e in params ) + a.push(expr(e)); + return cnew(cl,a); + case EThrow(e): + throw expr(e); + case ETry(e,n,_,ecatch): + var old = declared.length; + var oldTry = inTry; + try { + inTry = true; + var v : Dynamic = expr(e); + restore(old); + inTry = oldTry; + return v; + } catch( err : Stop ) { + inTry = oldTry; + throw err; + } catch( err : Dynamic ) { + // restore vars + restore(old); + inTry = oldTry; + // declare 'v' + declared.push({ n : n, old : locals.get(n) }); + locals.set(n,{ r : err }); + var v : Dynamic = expr(ecatch); + restore(old); + return v; + } + case EObject(fl): + var o = {}; + for( f in fl ) + set(o,f.name,expr(f.e)); + return o; + case ETernary(econd,e1,e2): + return if( expr(econd) == true ) expr(e1) else expr(e2); + case ESwitch(e, cases, def): + var val : Dynamic = expr(e); + var match = false; + for( c in cases ) { + for( v in c.values ) + if( expr(v) == val ) { + match = true; + break; + } + if( match ) { + val = expr(c.expr); + break; + } + } + if( !match ) + val = def == null ? null : expr(def); + return val; + case EMeta(_, _, e): + return expr(e); + case ECheckType(e,_): + return expr(e); + } + return null; + } + + function doWhileLoop(econd,e) { + var old = declared.length; + do { + try { + expr(e); + } catch( err : Stop ) { + switch(err) { + case SContinue: + case SBreak: break; + case SReturn: throw err; + } + } + } + while( expr(econd) == true ); + restore(old); + } + + function whileLoop(econd,e) { + var old = declared.length; + while( expr(econd) == true ) { + try { + expr(e); + } catch( err : Stop ) { + switch(err) { + case SContinue: + case SBreak: break; + case SReturn: throw err; + } + } + } + restore(old); + } + + function makeIterator( v : Dynamic ) : Iterator { + #if ((flash && !flash9) || (php && !php7 && haxe_ver < '4.0.0')) + if ( v.iterator != null ) v = v.iterator(); + #else + try v = v.iterator() catch( e : Dynamic ) {}; + #end + if( v.hasNext == null || v.next == null ) error(EInvalidIterator(v)); + return v; + } + + function forLoop(n,it,e) { + var old = declared.length; + declared.push({ n : n, old : locals.get(n) }); + var it = makeIterator(expr(it)); + while( it.hasNext() ) { + locals.set(n,{ r : it.next() }); + try { + expr(e); + } catch( err : Stop ) { + switch( err ) { + case SContinue: + case SBreak: break; + case SReturn: throw err; + } + } + } + restore(old); + } + + inline function isMap(o:Dynamic):Bool { + return (o is IMap); + } + + inline function getMapValue(map:Dynamic, key:Dynamic):Dynamic { + return cast(map, haxe.Constraints.IMap).get(key); + } + + inline function setMapValue(map:Dynamic, key:Dynamic, value:Dynamic):Void { + cast(map, haxe.Constraints.IMap).set(key, value); + } + + function get( o : Dynamic, f : String ) : Dynamic { + if ( o == null ) error(EInvalidAccess(f)); + return { + #if php + // https://github.com/HaxeFoundation/haxe/issues/4915 + try { + Reflect.getProperty(o, f); + } catch (e:Dynamic) { + Reflect.field(o, f); + } + #else + Reflect.getProperty(o, f); + #end + } + } + + function set( o : Dynamic, f : String, v : Dynamic ) : Dynamic { + if( o == null ) error(EInvalidAccess(f)); + Reflect.setProperty(o,f,v); + return v; + } + + function fcall( o : Dynamic, f : String, args : Array ) : Dynamic { + return call(o, get(o, f), args); + } + + function call( o : Dynamic, f : Dynamic, args : Array ) : Dynamic { + return Reflect.callMethod(o,f,args); + } + + function cnew( cl : String, args : Array ) : Dynamic { + #if gdscript + return null; + #else + var c = Type.resolveClass(cl); + if( c == null ) c = resolve(cl); + return Type.createInstance(c,args); + #end + } + +} diff --git a/hank/Output.hx b/hank/Output.hx index 8944472..42eb12c 100644 --- a/hank/Output.hx +++ b/hank/Output.hx @@ -163,6 +163,7 @@ class Output { default: return parseHaxeExpression(buffer); } + return null; } public static function parseHaxeExpression(buffer:HankBuffer):OutputType { diff --git a/hank/Parser.hx b/hank/Parser.hx new file mode 100644 index 0000000..e794cec --- /dev/null +++ b/hank/Parser.hx @@ -0,0 +1,1742 @@ +/* + * Copyright (C)2008-2017 Haxe Foundation + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ +package hank; +import hscript.Expr; + +#if gdscript +class Type { + public static function enumEq(a:Dynamic, b:Dynamic) { + return false; // TODO uh.... + } + + public static function enumConstructor(e:Dynamic) { + return ""; // TODO uh.... + } +} +#end + +enum Token { + TEof; + TConst( c : Const ); + TId( s : String ); + TOp( s : String ); + TPOpen; + TPClose; + TBrOpen; + TBrClose; + TDot; + TComma; + TSemicolon; + TBkOpen; + TBkClose; + TQuestion; + TDoubleDot; + TMeta( s : String ); + TPrepro( s : String ); +} + +class Parser { + + // config / variables + public var line : Int; + public var opChars : String; + public var identChars : String; + #if haxe3 + public var opPriority : Map; + public var opRightAssoc : Map; + #else + public var opPriority : Hash; + public var opRightAssoc : Hash; + #end + + /** + allows to check for #if / #else in code + **/ + public var preprocesorValues : Map = new Map(); + + /** + activate JSON compatiblity + **/ + public var allowJSON : Bool; + + /** + allow types declarations + **/ + public var allowTypes : Bool; + + /** + allow haxe metadata declarations + **/ + public var allowMetadata : Bool; + + /** + resume from parsing errors (when parsing incomplete code, during completion for example) + **/ + public var resumeErrors : Bool; + + // implementation + var input : String; + var readPos : Int; + + var char : Int; + var ops : Array; + var idents : Array; + var uid : Int = 0; + + #if hscriptPos + var origin : String; + var tokenMin : Int; + var tokenMax : Int; + var oldTokenMin : Int; + var oldTokenMax : Int; + var tokens : List<{ min : Int, max : Int, t : Token }>; + #else + static inline var p1 = 0; + static inline var tokenMin = 0; + static inline var tokenMax = 0; + #if haxe3 + var tokens : haxe.ds.GenericStack; + #else + var tokens : haxe.FastList; + #end + + #end + + + public function new() { + line = 1; + opChars = "+*/-=!><&|^%~"; + identChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_"; + var priorities:Array = [ + ["%"], + ["*", "/"], + ["+", "-"], + ["<<", ">>", ">>>"], + ["|", "&", "^"], + ["==", "!=", ">", "<", ">=", "<="], + ["..."], + ["&&"], + ["||"], + ["=","+=","-=","*=","/=","%=","<<=",">>=",">>>=","|=","&=","^=","=>"], + ["->"] + ]; + #if haxe3 + opPriority = new Map(); + opRightAssoc = new Map(); + #else + opPriority = new Hash(); + opRightAssoc = new Hash(); + #end + for( i in 0...priorities.length ) { + var p:Array = cast(priorities[i], Array); + for( x in p ) { + opPriority.set(x, i); + if( i == 9 ) opRightAssoc.set(x, true); + } + } + for( x in ["!", "++", "--", "~"] ) // unary "-" handled in parser directly! + opPriority.set(x, x == "++" || x == "--" ? -1 : -2); + } + + public inline function error( err, pmin, pmax ) { + if( !resumeErrors ) + #if hscriptPos + throw new Error(err, pmin, pmax, origin, line); + #else + throw err; + #end + } + + public function invalidChar(c) { + error(EInvalidChar(c), readPos-1, readPos-1); + } + + function initParser( origin ) { + // line=1 - don't reset line : it might be set manualy + preprocStack = []; + #if hscriptPos + this.origin = origin; + readPos = 0; + tokenMin = oldTokenMin = 0; + tokenMax = oldTokenMax = 0; + tokens = new List(); + #elseif haxe3 + tokens = new haxe.ds.GenericStack(); + #else + tokens = new haxe.FastList(); + #end + char = -1; + ops = new Array(); + idents = new Array(); + uid = 0; + for( i in 0...opChars.length ) + ops[opChars.charCodeAt(i)] = true; + for( i in 0...identChars.length ) + idents[identChars.charCodeAt(i)] = true; + } + + public function parseString( s : String, ?origin : String = "hscript" ) { + initParser(origin); + input = s; + readPos = 0; + var a = new Array(); + while( true ) { + var tk = token(); + if( tk == TEof ) break; + push(tk); + parseFullExpr(a); + } + return if( a.length == 1 ) a[0] else mk(EBlock(a),0); + } + + function unexpected( tk ) : Dynamic { + error(EUnexpected(tokenString(tk)),tokenMin,tokenMax); + return null; + } + + inline function push(tk) { + #if hscriptPos + tokens.push( { t : tk, min : tokenMin, max : tokenMax } ); + tokenMin = oldTokenMin; + tokenMax = oldTokenMax; + #else + tokens.add(tk); + #end + } + + inline function ensure(tk) { + var t = token(); + if( t != tk ) unexpected(t); + } + + inline function ensureToken(tk) { + var t = token(); + if( !Type.enumEq(t,tk) ) unexpected(t); + } + + function maybe(tk) { + var t = token(); + if( Type.enumEq(t, tk) ) + return true; + push(t); + return false; + } + + function getIdent() { + var tk = token(); + switch( tk ) { + case TId(id): return id; + default: + unexpected(tk); + return ""; + } + } + + inline function expr(e:Expr) { + #if hscriptPos + return e.e; + #else + return e; + #end + } + + inline function pmin(e:Expr) { + #if hscriptPos + return e == null ? 0 : e.pmin; + #else + return 0; + #end + } + + inline function pmax(e:Expr) { + #if hscriptPos + return e == null ? 0 : e.pmax; + #else + return 0; + #end + } + + inline function mk(e,?pmin,?pmax) : Expr { + #if hscriptPos + if( e == null ) return null; + if( pmin == null ) pmin = tokenMin; + if( pmax == null ) pmax = tokenMax; + return { e : e, pmin : pmin, pmax : pmax, origin : origin, line : line }; + #else + return e; + #end + } + + function isBlock(e) { + if( e == null ) return false; + return switch( expr(e) ) { + case EBlock(_), EObject(_), ESwitch(_): true; + case EFunction(_,e,_,_): isBlock(e); + case EVar(_, t, e): e != null ? isBlock(e) : t != null ? t.match(CTAnon(_)) : false; + case EIf(_,e1,e2): if( e2 != null ) isBlock(e2) else isBlock(e1); + case EBinop(_,_,e): isBlock(e); + case EUnop(_,prefix,e): !prefix && isBlock(e); + case EWhile(_,e): isBlock(e); + case EDoWhile(_,e): isBlock(e); + case EFor(_,_,e): isBlock(e); + case EReturn(e): e != null && isBlock(e); + case ETry(_, _, _, e): isBlock(e); + case EMeta(_, _, e): isBlock(e); + default: false; + } + } + + function parseFullExpr( exprs : Array ) { + var e = parseExpr(); + exprs.push(e); + + var tk = token(); + // this is a hack to support var a,b,c; with a single EVar + while( tk == TComma && e != null && expr(e).match(EVar(_)) ) { + e = parseStructure("var"); // next variable + exprs.push(e); + tk = token(); + } + + if( tk != TSemicolon && tk != TEof ) { + if( isBlock(e) ) + push(tk); + else + unexpected(tk); + } + } + + function parseObject(p1) { + // parse object + var fl = new Array(); + while( true ) { + var tk = token(); + var id = null; + switch( tk ) { + case TId(i): id = i; + case TConst(c): + if( !allowJSON ) + unexpected(tk); + switch( c ) { + case CString(s): id = s; + default: unexpected(tk); + } + case TBrClose: + break; + default: + unexpected(tk); + break; + } + ensure(TDoubleDot); + fl.push({ name : id, e : parseExpr() }); + tk = token(); + switch( tk ) { + case TBrClose: + break; + case TComma: + default: + unexpected(tk); + } + } + return parseExprNext(mk(EObject(fl),p1)); + } + + function parseExpr() { + var tk = token(); + #if hscriptPos + var p1 = tokenMin; + #end + switch( tk ) { + case TId(id): + var e = parseStructure(id); + if( e == null ) + e = mk(EIdent(id)); + return parseExprNext(e); + case TConst(c): + return parseExprNext(mk(EConst(c))); + case TPOpen: + tk = token(); + if( tk == TPClose ) { + ensureToken(TOp("->")); + var eret = parseExpr(); + return mk(EFunction([], mk(EReturn(eret),p1)), p1); + } + push(tk); + var e = parseExpr(); + tk = token(); + switch( tk ) { + case TPClose: + return parseExprNext(mk(EParent(e),p1,tokenMax)); + case TDoubleDot: + var t = parseType(); + tk = token(); + switch( tk ) { + case TPClose: + return parseExprNext(mk(ECheckType(e,t),p1,tokenMax)); + case TComma: + switch( expr(e) ) { + case EIdent(v): return parseLambda([{ name : v, t : t }], pmin(e)); + default: + } + default: + } + case TComma: + switch( expr(e) ) { + case EIdent(v): return parseLambda([{name:v}], pmin(e)); + default: + } + default: + } + return unexpected(tk); + case TBrOpen: + tk = token(); + switch( tk ) { + case TBrClose: + return parseExprNext(mk(EObject([]),p1)); + case TId(_): + var tk2 = token(); + push(tk2); + push(tk); + switch( tk2 ) { + case TDoubleDot: + return parseExprNext(parseObject(p1)); + default: + } + case TConst(c): + if( allowJSON ) { + switch( c ) { + case CString(_): + var tk2 = token(); + push(tk2); + push(tk); + switch( tk2 ) { + case TDoubleDot: + return parseExprNext(parseObject(p1)); + default: + } + default: + push(tk); + } + } else + push(tk); + default: + push(tk); + } + var a = new Array(); + while( true ) { + parseFullExpr(a); + tk = token(); + if( tk == TBrClose || (resumeErrors && tk == TEof) ) + break; + push(tk); + } + return mk(EBlock(a),p1); + case TOp(op): + if( op == "-" ) { + var start = tokenMin; + var e = parseExpr(); + if( e == null ) + return makeUnop(op,e); + switch( expr(e) ) { + case EConst(CInt(i)): + return mk(EConst(CInt(-i)), start, pmax(e)); + case EConst(CFloat(f)): + return mk(EConst(CFloat(-f)), start, pmax(e)); + default: + return makeUnop(op,e); + } + } + if( opPriority.get(op) < 0 ) + return makeUnop(op,parseExpr()); + return unexpected(tk); + case TBkOpen: + var a = new Array(); + tk = token(); + while( tk != TBkClose && (!resumeErrors || tk != TEof) ) { + push(tk); + a.push(parseExpr()); + tk = token(); + if( tk == TComma ) + tk = token(); + } + if( a.length == 1 && a[0] != null ) + switch( expr(a[0]) ) { + case EFor(_), EWhile(_), EDoWhile(_): + var tmp = "__a_" + (uid++); + var e = mk(EBlock([ + mk(EVar(tmp, null, mk(EArrayDecl([]), p1)), p1), + mapCompr(tmp, a[0]), + mk(EIdent(tmp),p1), + ]),p1); + return parseExprNext(e); + default: + } + return parseExprNext(mk(EArrayDecl(a), p1)); + case TMeta(id) if( allowMetadata ): + var args = parseMetaArgs(); + return mk(EMeta(id, args, parseExpr()),p1); + default: + return unexpected(tk); + } + } + + function parseLambda( args : Array, pmin ) { + while( true ) { + var id = getIdent(); + var t = maybe(TDoubleDot) ? parseType() : null; + args.push({ name : id, t : t }); + var tk = token(); + switch( tk ) { + case TComma: + case TPClose: + break; + default: + unexpected(tk); + break; + } + } + ensureToken(TOp("->")); + var eret = parseExpr(); + return mk(EFunction(args, mk(EReturn(eret),pmin)), pmin); + } + + function parseMetaArgs() { + var tk = token(); + if( tk != TPOpen ) { + push(tk); + return null; + } + var args = []; + tk = token(); + if( tk != TPClose ) { + push(tk); + while( true ) { + args.push(parseExpr()); + switch( token() ) { + case TComma: + case TPClose: + break; + case tk: + unexpected(tk); + } + } + } + return args; + } + + function mapCompr( tmp : String, e : Expr ) { + if( e == null ) return null; + var edef = switch( expr(e) ) { + case EFor(v, it, e2): + EFor(v, it, mapCompr(tmp, e2)); + case EWhile(cond, e2): + EWhile(cond, mapCompr(tmp, e2)); + case EDoWhile(cond, e2): + EDoWhile(cond, mapCompr(tmp, e2)); + case EIf(cond, e1, e2) if( e2 == null ): + EIf(cond, mapCompr(tmp, e1), null); + case EBlock([e]): + EBlock([mapCompr(tmp, e)]); + case EParent(e2): + EParent(mapCompr(tmp, e2)); + default: + ECall( mk(EField(mk(EIdent(tmp), pmin(e), pmax(e)), "push"), pmin(e), pmax(e)), [e]); + } + return mk(edef, pmin(e), pmax(e)); + } + + function makeUnop( op, e ) { + if( e == null && resumeErrors ) + return null; + return switch( expr(e) ) { + case EBinop(bop, e1, e2): mk(EBinop(bop, makeUnop(op, e1), e2), pmin(e1), pmax(e2)); + case ETernary(e1, e2, e3): mk(ETernary(makeUnop(op, e1), e2, e3), pmin(e1), pmax(e3)); + default: mk(EUnop(op,true,e),pmin(e),pmax(e)); + } + } + + function makeBinop( op, e1, e ) { + if( e == null && resumeErrors ) + return mk(EBinop(op,e1,e),pmin(e1),pmax(e1)); + return switch( expr(e) ) { + case EBinop(op2,e2,e3): + if( opPriority.get(op) <= opPriority.get(op2) && !opRightAssoc.exists(op) ) + mk(EBinop(op2,makeBinop(op,e1,e2),e3),pmin(e1),pmax(e3)); + else + mk(EBinop(op, e1, e), pmin(e1), pmax(e)); + case ETernary(e2,e3,e4): + if( opRightAssoc.exists(op) ) + mk(EBinop(op,e1,e),pmin(e1),pmax(e)); + else + mk(ETernary(makeBinop(op, e1, e2), e3, e4), pmin(e1), pmax(e)); + default: + mk(EBinop(op,e1,e),pmin(e1),pmax(e)); + } + } + + function parseStructure(id) { + #if hscriptPos + var p1 = tokenMin; + #end + return switch( id ) { + case "if": + ensure(TPOpen); + var cond = parseExpr(); + ensure(TPClose); + var e1 = parseExpr(); + var e2 = null; + var semic = false; + var tk = token(); + if( tk == TSemicolon ) { + semic = true; + tk = token(); + } + if( Type.enumEq(tk,TId("else")) ) + e2 = parseExpr(); + else { + push(tk); + if( semic ) push(TSemicolon); + } + mk(EIf(cond,e1,e2),p1,(e2 == null) ? tokenMax : pmax(e2)); + case "var": + var ident = getIdent(); + var tk = token(); + var t = null; + if( tk == TDoubleDot && allowTypes ) { + t = parseType(); + tk = token(); + } + var e = null; + if( Type.enumEq(tk,TOp("=")) ) + e = parseExpr(); + else + push(tk); + mk(EVar(ident,t,e),p1,(e == null) ? tokenMax : pmax(e)); + case "while": + var econd = parseExpr(); + var e = parseExpr(); + mk(EWhile(econd,e),p1,pmax(e)); + case "do": + var e = parseExpr(); + var tk = token(); + switch(tk) + { + case TId("while"): // Valid + default: unexpected(tk); + } + var econd = parseExpr(); + mk(EDoWhile(econd,e),p1,pmax(econd)); + case "for": + ensure(TPOpen); + var vname = getIdent(); + ensureToken(TId("in")); + var eiter = parseExpr(); + ensure(TPClose); + var e = parseExpr(); + mk(EFor(vname,eiter,e),p1,pmax(e)); + case "break": mk(EBreak); + case "continue": mk(EContinue); + case "else": unexpected(TId(id)); + case "inline": + if( !maybe(TId("function")) ) unexpected(TId("inline")); + return parseStructure("function"); + case "function": + var tk = token(); + var name = null; + switch( tk ) { + case TId(id): name = id; + default: push(tk); + } + var inf = parseFunctionDecl(); + mk(EFunction(inf.args, inf.body, name, inf.ret),p1,pmax(inf.body)); + case "return": + var tk = token(); + push(tk); + var e = if( tk == TSemicolon ) null else parseExpr(); + mk(EReturn(e),p1,if( e == null ) tokenMax else pmax(e)); + case "new": + var a = new Array(); + a.push(getIdent()); + while( true ) { + var tk = token(); + switch( tk ) { + case TDot: + a.push(getIdent()); + case TPOpen: + break; + default: + unexpected(tk); + break; + } + } + var args = parseExprList(TPClose); + mk(ENew(a.join("."),args),p1); + case "throw": + var e = parseExpr(); + mk(EThrow(e),p1,pmax(e)); + case "try": + var e = parseExpr(); + ensureToken(TId("catch")); + ensure(TPOpen); + var vname = getIdent(); + ensure(TDoubleDot); + var t = null; + if( allowTypes ) + t = parseType(); + else + ensureToken(TId("Dynamic")); + ensure(TPClose); + var ec = parseExpr(); + mk(ETry(e, vname, t, ec), p1, pmax(ec)); + case "switch": + var e = parseExpr(); + var def = null, cases = []; + ensure(TBrOpen); + while( true ) { + var tk = token(); + switch( tk ) { + case TId("case"): + var c = { values : [], expr : null }; + cases.push(c); + while( true ) { + var e = parseExpr(); + c.values.push(e); + tk = token(); + switch( tk ) { + case TComma: + // next expr + case TDoubleDot: + break; + default: + unexpected(tk); + break; + } + } + var exprs = []; + while( true ) { + tk = token(); + push(tk); + switch( tk ) { + case TId("case"), TId("default"), TBrClose: + break; + case TEof if( resumeErrors ): + break; + default: + parseFullExpr(exprs); + } + } + c.expr = if( exprs.length == 1) + exprs[0]; + else if( exprs.length == 0 ) + mk(EBlock([]), tokenMin, tokenMin); + else + mk(EBlock(exprs), pmin(exprs[0]), pmax(exprs[exprs.length - 1])); + case TId("default"): + if( def != null ) unexpected(tk); + ensure(TDoubleDot); + var exprs = []; + while( true ) { + tk = token(); + push(tk); + switch( tk ) { + case TId("case"), TId("default"), TBrClose: + break; + case TEof if( resumeErrors ): + break; + default: + parseFullExpr(exprs); + } + } + def = if( exprs.length == 1) + exprs[0]; + else if( exprs.length == 0 ) + mk(EBlock([]), tokenMin, tokenMin); + else + mk(EBlock(exprs), pmin(exprs[0]), pmax(exprs[exprs.length - 1])); + case TBrClose: + break; + default: + unexpected(tk); + break; + } + } + mk(ESwitch(e, cases, def), p1, tokenMax); + default: + null; + } + } + + function parseExprNext( e1 : Expr ) { + var tk = token(); + switch( tk ) { + case TOp(op): + + if( op == "->" ) { + // single arg reinterpretation of `f -> e` , `(f) -> e` and `(f:T) -> e` + switch( expr(e1) ) { + case EIdent(i), EParent(expr(_) => EIdent(i)): + var eret = parseExpr(); + return mk(EFunction([{ name : i }], mk(EReturn(eret),pmin(eret))), pmin(e1)); + case ECheckType(expr(_) => EIdent(i), t): + var eret = parseExpr(); + return mk(EFunction([{ name : i, t : t }], mk(EReturn(eret),pmin(eret))), pmin(e1)); + default: + } + unexpected(tk); + } + + if( opPriority.get(op) == -1 ) { + if( isBlock(e1) || switch(expr(e1)) { case EParent(_): true; default: false; } ) { + push(tk); + return e1; + } + return parseExprNext(mk(EUnop(op,false,e1),pmin(e1))); + } + return makeBinop(op,e1,parseExpr()); + case TDot: + var field = getIdent(); + return parseExprNext(mk(EField(e1,field),pmin(e1))); + case TPOpen: + return parseExprNext(mk(ECall(e1,parseExprList(TPClose)),pmin(e1))); + case TBkOpen: + var e2 = parseExpr(); + ensure(TBkClose); + return parseExprNext(mk(EArray(e1,e2),pmin(e1))); + case TQuestion: + var e2 = parseExpr(); + ensure(TDoubleDot); + var e3 = parseExpr(); + return mk(ETernary(e1,e2,e3),pmin(e1),pmax(e3)); + default: + push(tk); + return e1; + } + } + + function parseFunctionArgs() { + var args = new Array(); + var tk = token(); + if( tk != TPClose ) { + var done = false; + while( !done ) { + var name = null, opt = false; + switch( tk ) { + case TQuestion: + opt = true; + tk = token(); + default: + } + switch( tk ) { + case TId(id): name = id; + default: + unexpected(tk); + break; + } + var arg : Argument = { name : name }; + args.push(arg); + if( opt ) arg.opt = true; + if( allowTypes ) { + if( maybe(TDoubleDot) ) + arg.t = parseType(); + if( maybe(TOp("=")) ) + arg.value = parseExpr(); + } + tk = token(); + switch( tk ) { + case TComma: + tk = token(); + case TPClose: + done = true; + default: + unexpected(tk); + } + } + } + return args; + } + + function parseFunctionDecl() { + ensure(TPOpen); + var args = parseFunctionArgs(); + var ret = null; + if( allowTypes ) { + var tk = token(); + if( tk != TDoubleDot ) + push(tk); + else + ret = parseType(); + } + return { args : args, ret : ret, body : parseExpr() }; + } + + function parsePath() { + var path = [getIdent()]; + while( true ) { + var t = token(); + if( t != TDot ) { + push(t); + break; + } + path.push(getIdent()); + } + return path; + } + + function parseType() : CType { + var t = token(); + switch( t ) { + case TId(v): + push(t); + var path = parsePath(); + var params = null; + t = token(); + switch( t ) { + case TOp(op): + if( op == "<" ) { + params = []; + while( true ) { + params.push(parseType()); + t = token(); + switch( t ) { + case TComma: continue; + case TOp(op): + if( op == ">" ) break; + if( op.charCodeAt(0) == ">".code ) { + #if hscriptPos + tokens.add({ t : TOp(op.substr(1)), min : tokenMax - op.length - 1, max : tokenMax }); + #else + tokens.add(TOp(op.substr(1))); + #end + break; + } + default: + } + unexpected(t); + break; + } + } else + push(t); + default: + push(t); + } + return parseTypeNext(CTPath(path, params)); + case TPOpen: + var a = token(), + b = token(); + + push(b); + push(a); + + function withReturn(args) { + switch token() { // I think it wouldn't hurt if ensure used enumEq + case TOp('->'): + case t: unexpected(t); + } + + return CTFun(args, parseType()); + } + + switch [a, b] { + case [TPClose, _] | [TId(_), TDoubleDot]: + + var args = [for (arg in parseFunctionArgs()) { + switch arg.value { + case null: + case v: + error(ECustom('Default values not allowed in function types'), #if hscriptPos v.pmin, v.pmax #else 0, 0 #end); + } + + CTNamed(arg.name, if (arg.opt) CTOpt(arg.t) else arg.t); + }]; + + return withReturn(args); + default: + + var t = parseType(); + return switch token() { + case TComma: + var args = [t]; + + while (true) { + args.push(parseType()); + if (!maybe(TComma)) break; + } + ensure(TPClose); + withReturn(args); + case TPClose: + parseTypeNext(CTParent(t)); + case t: unexpected(t); + } + } + case TBrOpen: + var fields = []; + var meta = null; + while( true ) { + t = token(); + switch( t ) { + case TBrClose: break; + case TId("var"): + var name = getIdent(); + ensure(TDoubleDot); + fields.push( { name : name, t : parseType(), meta : meta } ); + meta = null; + ensure(TSemicolon); + case TId(name): + ensure(TDoubleDot); + fields.push( { name : name, t : parseType(), meta : meta } ); + t = token(); + switch( t ) { + case TComma: + case TBrClose: break; + default: unexpected(t); + } + case TMeta(name): + if( meta == null ) meta = []; + meta.push({ name : name, params : parseMetaArgs() }); + default: + unexpected(t); + break; + } + } + return parseTypeNext(CTAnon(fields)); + default: + return unexpected(t); + } + return null; + } + + function parseTypeNext( t : CType ) { + var tk = token(); + switch( tk ) { + case TOp(op): + if( op != "->" ) { + push(tk); + return t; + } + default: + push(tk); + return t; + } + var t2 = parseType(); + switch( t2 ) { + case CTFun(args, _): + args.unshift(t); + return t2; + default: + return CTFun([t], t2); + } + } + + function parseExprList( etk ) { + var args = new Array(); + var tk = token(); + if( tk == etk ) + return args; + push(tk); + while( true ) { + args.push(parseExpr()); + tk = token(); + switch( tk ) { + case TComma: + default: + if( tk == etk ) break; + unexpected(tk); + break; + } + } + return args; + } + + // ------------------------ module ------------------------------- + + public function parseModule( content : String, ?origin : String = "hscript" ) { + initParser(origin); + input = content; + readPos = 0; + allowTypes = true; + allowMetadata = true; + var decls = []; + while( true ) { + var tk = token(); + if( tk == TEof ) break; + push(tk); + decls.push(parseModuleDecl()); + } + return decls; + } + + function parseMetadata() : Metadata { + var meta = []; + while( true ) { + var tk = token(); + switch( tk ) { + case TMeta(name): + meta.push({ name : name, params : parseMetaArgs() }); + default: + push(tk); + break; + } + } + return meta; + } + + function parseParams() { + if( maybe(TOp("<")) ) + error(EInvalidOp("Unsupported class type parameters"), readPos, readPos); + return {}; + } + + function parseModuleDecl() : ModuleDecl { + var meta = parseMetadata(); + var ident = getIdent(); + var isPrivate = false, isExtern = false; + while( true ) { + switch( ident ) { + case "private": + isPrivate = true; + case "extern": + isExtern = true; + default: + break; + } + ident = getIdent(); + } + switch( ident ) { + case "package": + var path = parsePath(); + ensure(TSemicolon); + return DPackage(path); + case "import": + var path = [getIdent()]; + var star = false; + while( true ) { + var t = token(); + if( t != TDot ) { + push(t); + break; + } + t = token(); + switch( t ) { + case TId(id): + path.push(id); + case TOp("*"): + star = true; + default: + unexpected(t); + } + } + ensure(TSemicolon); + return DImport(path, star); + case "class": + var name = getIdent(); + var params = parseParams(); + var extend = null; + var implement = []; + + while( true ) { + var t = token(); + switch( t ) { + case TId("extends"): + extend = parseType(); + case TId("implements"): + implement.push(parseType()); + default: + push(t); + break; + } + } + + var fields = []; + ensure(TBrOpen); + while( !maybe(TBrClose) ) + fields.push(parseField()); + + return DClass({ + name : name, + meta : meta, + params : params, + extend : extend, + implement : implement, + fields : fields, + isPrivate : isPrivate, + isExtern : isExtern, + }); + case "typedef": + var name = getIdent(); + var params = parseParams(); + ensureToken(TOp("=")); + var t = parseType(); + return DTypedef({ + name : name, + meta : meta, + params : params, + isPrivate : isPrivate, + t : t, + }); + default: + unexpected(TId(ident)); + } + return null; + } + + function parseField() : FieldDecl { + var meta = parseMetadata(); + var access = []; + while( true ) { + var id = getIdent(); + switch( id ) { + case "override": + access.push(AOverride); + case "public": + access.push(APublic); + case "private": + access.push(APrivate); + case "inline": + access.push(AInline); + case "static": + access.push(AStatic); + case "macro": + access.push(AMacro); + case "function": + var name = getIdent(); + var inf = parseFunctionDecl(); + return { + name : name, + meta : meta, + access : access, + kind : KFunction({ + args : inf.args, + expr : inf.body, + ret : inf.ret, + }), + }; + case "var": + var name = getIdent(); + var get = null, set = null; + if( maybe(TPOpen) ) { + get = getIdent(); + ensure(TComma); + set = getIdent(); + ensure(TPClose); + } + var type = maybe(TDoubleDot) ? parseType() : null; + var expr = maybe(TOp("=")) ? parseExpr() : null; + + if( expr != null ) { + if( isBlock(expr) ) + maybe(TSemicolon); + else + ensure(TSemicolon); + } else if( type != null && type.match(CTAnon(_)) ) { + maybe(TSemicolon); + } else + ensure(TSemicolon); + + return { + name : name, + meta : meta, + access : access, + kind : KVar({ + get : get, + set : set, + type : type, + expr : expr, + }), + }; + default: + unexpected(TId(id)); + break; + } + } + return null; + } + + // ------------------------ lexing ------------------------------- + + inline function readChar() { + return StringTools.fastCodeAt(input, readPos++); + } + + function readString( until ) { + var c = 0; + var b = new StringBuf(); + var esc = false; + var old = line; + var s = input; + #if hscriptPos + var p1 = readPos - 1; + #end + while( true ) { + var c = readChar(); + if( StringTools.isEof(c) ) { + line = old; + error(EUnterminatedString, p1, p1); + break; + } + if( esc ) { + esc = false; + switch( c ) { + case 'n'.code: b.addChar('\n'.code); + case 'r'.code: b.addChar('\r'.code); + case 't'.code: b.addChar('\t'.code); + case "'".code, '"'.code, '\\'.code: b.addChar(c); + case '/'.code: if( allowJSON ) b.addChar(c) else invalidChar(c); + case "u".code: + if( !allowJSON ) invalidChar(c); + var k = 0; + for( i in 0...4 ) { + k <<= 4; + var char = readChar(); + switch( char ) { + case 48,49,50,51,52,53,54,55,56,57: // 0-9 + k += char - 48; + case 65,66,67,68,69,70: // A-F + k += char - 55; + case 97,98,99,100,101,102: // a-f + k += char - 87; + default: + if( StringTools.isEof(char) ) { + line = old; + error(EUnterminatedString, p1, p1); + } + invalidChar(char); + } + } + b.addChar(k); + default: invalidChar(c); + } + } else if( c == 92 ) + esc = true; + else if( c == until ) + break; + else { + if( c == 10 ) line++; + b.addChar(c); + } + } + return b.toString(); + } + + function token() { + #if hscriptPos + var t = tokens.pop(); + if( t != null ) { + tokenMin = t.min; + tokenMax = t.max; + return t.t; + } + oldTokenMin = tokenMin; + oldTokenMax = tokenMax; + tokenMin = (this.char < 0) ? readPos : readPos - 1; + var t = _token(); + tokenMax = (this.char < 0) ? readPos - 1 : readPos - 2; + return t; + } + + function _token() { + #else + if( !tokens.isEmpty() ) + return tokens.pop(); + #end + var char; + if( this.char < 0 ) + char = readChar(); + else { + char = this.char; + this.char = -1; + } + while( true ) { + if( StringTools.isEof(char) ) { + this.char = char; + return TEof; + } + switch( char ) { + case 0: + return TEof; + case 32,9,13: // space, tab, CR + #if hscriptPos + tokenMin++; + #end + case 10: line++; // LF + #if hscriptPos + tokenMin++; + #end + case 48,49,50,51,52,53,54,55,56,57: // 0...9 + var n = (char - 48) * 1.0; + var exp = 0.; + while( true ) { + char = readChar(); + exp *= 10; + switch( char ) { + case 48,49,50,51,52,53,54,55,56,57: + n = n * 10 + (char - 48); + case "e".code, "E".code: + var tk = token(); + var pow : Null = null; + switch( tk ) { + case TConst(CInt(e)): pow = e; + case TOp("-"): + tk = token(); + switch( tk ) { + case TConst(CInt(e)): pow = -e; + default: push(tk); + } + default: + push(tk); + } + if( pow == null ) + invalidChar(char); + return TConst(CFloat((Math.pow(10, pow) / exp) * n * 10)); + case ".".code: + if( exp > 0 ) { + // in case of '0...' + if( exp == 10 && readChar() == ".".code ) { + push(TOp("...")); + var i = Std.int(n); + return TConst( (i == n) ? CInt(i) : CFloat(n) ); + } + invalidChar(char); + } + exp = 1.; + case "x".code: + if( n > 0 || exp > 0 ) + invalidChar(char); + // read hexa + #if haxe3 + var n = 0; + while( true ) { + char = readChar(); + switch( char ) { + case 48,49,50,51,52,53,54,55,56,57: // 0-9 + n = (n << 4) + char - 48; + case 65,66,67,68,69,70: // A-F + n = (n << 4) + (char - 55); + case 97,98,99,100,101,102: // a-f + n = (n << 4) + (char - 87); + default: + this.char = char; + return TConst(CInt(n)); + } + } + #else + var n = haxe.Int32.ofInt(0); + while( true ) { + char = readChar(); + switch( char ) { + case 48,49,50,51,52,53,54,55,56,57: // 0-9 + n = haxe.Int32.add(haxe.Int32.shl(n,4), cast (char - 48)); + case 65,66,67,68,69,70: // A-F + n = haxe.Int32.add(haxe.Int32.shl(n,4), cast (char - 55)); + case 97,98,99,100,101,102: // a-f + n = haxe.Int32.add(haxe.Int32.shl(n,4), cast (char - 87)); + default: + this.char = char; + // we allow to parse hexadecimal Int32 in Neko, but when the value will be + // evaluated by Interpreter, a failure will occur if no Int32 operation is + // performed + var v = try CInt(haxe.Int32.toInt(n)) catch( e : Dynamic ) CInt32(n); + return TConst(v); + } + } + #end + default: + this.char = char; + var i = Std.int(n); + return TConst( (exp > 0) ? CFloat(n * 10 / exp) : ((i == n) ? CInt(i) : CFloat(n)) ); + } + } + case ";".code: return TSemicolon; + case "(".code: return TPOpen; + case ")".code: return TPClose; + case ",".code: return TComma; + case ".".code: + char = readChar(); + switch( char ) { + case 48,49,50,51,52,53,54,55,56,57: + var n = char - 48; + var exp = 1; + while( true ) { + char = readChar(); + exp *= 10; + switch( char ) { + case 48,49,50,51,52,53,54,55,56,57: + n = n * 10 + (char - 48); + default: + this.char = char; + return TConst( CFloat(n/exp) ); + } + } + case ".".code: + char = readChar(); + if( char != ".".code ) + invalidChar(char); + return TOp("..."); + default: + this.char = char; + return TDot; + } + case "{".code: return TBrOpen; + case "}".code: return TBrClose; + case "[".code: return TBkOpen; + case "]".code: return TBkClose; + case "'".code, '"'.code: return TConst( CString(readString(char)) ); + case "?".code: return TQuestion; + case ":".code: return TDoubleDot; + case '='.code: + char = readChar(); + if( char == '='.code ) + return TOp("=="); + else if ( char == '>'.code ) + return TOp("=>"); + this.char = char; + return TOp("="); + case '@'.code: + char = readChar(); + if( idents[char] || char == ':'.code ) { + var id = String.fromCharCode(char); + while( true ) { + char = readChar(); + if( !idents[char] ) { + this.char = char; + return TMeta(id); + } + id += String.fromCharCode(char); + } + } + invalidChar(char); + case '#'.code: + char = readChar(); + if( idents[char] ) { + var id = String.fromCharCode(char); + while( true ) { + char = readChar(); + if( !idents[char] ) { + this.char = char; + return preprocess(id); + } + id += String.fromCharCode(char); + } + } + invalidChar(char); + default: + if( ops[char] ) { + var op = String.fromCharCode(char); + while( true ) { + char = readChar(); + if( StringTools.isEof(char) ) char = 0; + if( !ops[char] ) { + this.char = char; + return TOp(op); + } + var pop = op; + op += String.fromCharCode(char); + if( !opPriority.exists(op) && opPriority.exists(pop) ) { + if( op == "//" || op == "/*" ) + return tokenComment(op,char); + this.char = char; + return TOp(pop); + } + } + } + if( idents[char] ) { + var id = String.fromCharCode(char); + while( true ) { + char = readChar(); + if( StringTools.isEof(char) ) char = 0; + if( !idents[char] ) { + this.char = char; + return TId(id); + } + id += String.fromCharCode(char); + } + } + invalidChar(char); + } + char = readChar(); + } + return null; + } + + function preprocValue( id : String ) : Dynamic { + return preprocesorValues.get(id); + } + + var preprocStack : Array<{ r : Bool }>; + + function parsePreproCond() { + var tk = token(); + return switch( tk ) { + case TPOpen: + push(TPOpen); + parseExpr(); + case TId(id): + mk(EIdent(id), tokenMin, tokenMax); + case TOp("!"): + mk(EUnop("!", true, parsePreproCond()), tokenMin, tokenMax); + default: + unexpected(tk); + } + } + + function evalPreproCond( e : Expr ) { + switch( expr(e) ) { + case EIdent(id): + return preprocValue(id) != null; + case EUnop("!", _, e): + return !evalPreproCond(e); + case EParent(e): + return evalPreproCond(e); + case EBinop("&&", e1, e2): + return evalPreproCond(e1) && evalPreproCond(e2); + case EBinop("||", e1, e2): + return evalPreproCond(e1) || evalPreproCond(e2); + default: + error(EInvalidPreprocessor("Can't eval " + expr(e).getName()), readPos, readPos); + return false; + } + return false; + } + + function preprocess( id : String ) : Token { + switch( id ) { + case "if": + var e = parsePreproCond(); + if( evalPreproCond(e) ) { + preprocStack.push({ r : true }); + return token(); + } + preprocStack.push({ r : false }); + skipTokens(); + return token(); + case "else", "elseif" if( preprocStack.length > 0 ): + if( preprocStack[preprocStack.length - 1].r ) { + preprocStack[preprocStack.length - 1].r = false; + skipTokens(); + return token(); + } else if( id == "else" ) { + preprocStack.pop(); + preprocStack.push({ r : true }); + return token(); + } else { + // elseif + preprocStack.pop(); + return preprocess("if"); + } + case "end" if( preprocStack.length > 0 ): + preprocStack.pop(); + return token(); + default: + return TPrepro(id); + } + } + + function skipTokens() { + var spos = preprocStack.length - 1; + var obj = preprocStack[spos]; + var pos = readPos; + while( true ) { + var tk = token(); + if( tk == TEof ) + error(EInvalidPreprocessor("Unclosed"), pos, pos); + if( preprocStack[spos] != obj ) { + push(tk); + break; + } + } + } + + function tokenComment( op : String, char : Int ) { + var c = op.charCodeAt(1); + var s = input; + if( c == '/'.code ) { // comment + while( char != '\r'.code && char != '\n'.code ) { + char = readChar(); + if( StringTools.isEof(char) ) break; + } + this.char = char; + return token(); + } + if( c == '*'.code ) { /* comment */ + var old = line; + if( op == "/**/" ) { + this.char = char; + return token(); + } + while( true ) { + while( char != '*'.code ) { + if( char == '\n'.code ) line++; + char = readChar(); + if( StringTools.isEof(char) ) { + line = old; + error(EUnterminatedComment, tokenMin, tokenMin); + break; + } + } + char = readChar(); + if( StringTools.isEof(char) ) { + line = old; + error(EUnterminatedComment, tokenMin, tokenMin); + break; + } + if( char == '/'.code ) + break; + } + return token(); + } + this.char = char; + return TOp(op); + } + + function constString( c ) { + return switch(c) { + case CInt(v): Std.string(v); + case CFloat(f): Std.string(f); + case CString(s): s; // TODO : escape + quote + #if !haxe3 + case CInt32(v): Std.string(v); + #end + } + } + + function tokenString( t ) { + return switch( t ) { + case TEof: ""; + case TConst(c): constString(c); + case TId(s): s; + case TOp(s): s; + case TPOpen: "("; + case TPClose: ")"; + case TBrOpen: "{"; + case TBrClose: "}"; + case TDot: "."; + case TComma: ","; + case TSemicolon: ";"; + case TBkOpen: "["; + case TBkClose: "]"; + case TQuestion: "?"; + case TDoubleDot: ":"; + case TMeta(id): "@" + id; + case TPrepro(id): "#" + id; + } + } + +}