From 18a12d60e2823f55b970b4766d247024f065fe2d Mon Sep 17 00:00:00 2001 From: Davvex87 <102032123+Davvex87@users.noreply.github.com> Date: Sun, 15 Mar 2026 00:37:14 +0000 Subject: [PATCH 01/12] added string interpolation support --- hscript/Expr.hx | 7 ++- hscript/Parser.hx | 127 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 132 insertions(+), 2 deletions(-) diff --git a/hscript/Expr.hx b/hscript/Expr.hx index 7d5803c4..4e879e03 100644 --- a/hscript/Expr.hx +++ b/hscript/Expr.hx @@ -21,10 +21,15 @@ */ package hscript; +enum StringKind { + DoubleQuotes; + SingleQuotes; +} + enum Const { CInt( v : Int ); CFloat( f : Float ); - CString( s : String ); + CString( s : String , ?kind : StringKind); } #if hscriptPos diff --git a/hscript/Parser.hx b/hscript/Parser.hx index 9f917309..bc3f8ef4 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -354,6 +354,21 @@ class Parser { e = mk(EIdent(id)); return isBlock(e) ? e : parseExprNext(e); case TConst(c): + if (c.match(CString(_, SingleQuotes))) + { + var ex = makeInterpolatedStr(tk); + if (ex.length == 1) + return parseExprNext(ex[0]); + else + { + var result = ex[0]; + for (i in 1...ex.length) + { + result = makeBinop("+", result, ex[i]); + } + return parseExprNext(mk(EParent(result), p1)); + } + } return parseExprNext(mk(EConst(c))); case TPOpen: tk = token(); @@ -644,6 +659,115 @@ class Parser { } } + function makeInterpolatedStr(tk:Token):Array { + var ex = new Array(); + var s = switch(tk) + { + case TConst(c): + switch(c) + { + case CString(s): + s; + case _: + null; + } + case _: + null; + } + + if (s == null) + error(ECustom("Invalid string literal"), tokenMin, tokenMax); + + var nextStr = ""; + + var i = 0; + var c = s.charCodeAt(i); + while (true) + { + if (StringTools.isEof(c)) + { + ex.push(mk(EConst(CString(nextStr)), tokenMin, tokenMax)); + break; + } + + if (c != "$".code) + { + nextStr += String.fromCharCode(c); + c = s.charCodeAt(++i); + continue; + } + + c = s.charCodeAt(++i); + if (c >= 48 && c <= 57 || c == "$".code) + { + nextStr += "$" + String.fromCharCode(c); + c = s.charCodeAt(++i); + continue; + } + + if( idents[c] ) { + var id = String.fromCharCode(c); + while( true ) { + c = s.charCodeAt(++i); + if( StringTools.isEof(c) ) c = 0; + if( !idents[c] ) { + break; + } + id += String.fromCharCode(c); + } + ex.push(mk(EConst(CString(nextStr)), tokenMin, tokenMax)); + ex.push(mk(EIdent(id), tokenMin, tokenMax)); + nextStr = ""; + } + else if (c == "{".code) + { + var lastInput = input; + var lastReadPos = readPos; + var lastChar = char; + var lastTokens = tokens; + input = s; + readPos = i + 1; + char = -1; + #if hscriptPos + tokens = new List(); + #else + tokens = new haxe.ds.GenericStack(); + #end + var a = new Array(); + var brOpens = 0; + while( true ) { + var tk = token(); + if ( tk == TBrOpen ) + brOpens++; + else if ( tk == TBrClose ) + { + if (brOpens == 0) + break; + brOpens--; + } + if( tk == TEof ) break; + push(tk); + parseFullExpr(a); + } + ex.push(mk(EConst(CString(nextStr)), tokenMin, tokenMax)); + ex.push(mk(EBlock(a),0)); + nextStr = ""; + input = lastInput; + i = readPos; + c = s.charCodeAt(i); + readPos = lastReadPos; + char = lastChar; + tokens = lastTokens; + } + else + { + nextStr += "$"; + i--; + } + } + return ex; + } + function parseStructure(id) { #if hscriptPos var p1 = tokenMin; @@ -1589,7 +1713,8 @@ class Parser { case "}".code: return TBrClose; case "[".code: return TBkOpen; case "]".code: return TBkClose; - case "'".code, '"'.code: return TConst( CString(readString(char)) ); + case "'".code: return TConst( CString(readString(char), SingleQuotes) ); + case '"'.code: return TConst( CString(readString(char), DoubleQuotes) ); case "?".code: char = readChar(); if( char == ".".code ) From 972cc6c31645ec6c4d7055c17b73773aae554ec6 Mon Sep 17 00:00:00 2001 From: Davvex87 <102032123+Davvex87@users.noreply.github.com> Date: Sun, 15 Mar 2026 01:08:30 +0000 Subject: [PATCH 02/12] missing semicolon fix auto add a "shadow semicolon" at the end of the interpolated block if one is missing --- hscript/Parser.hx | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/hscript/Parser.hx b/hscript/Parser.hx index bc3f8ef4..7894c58e 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -725,27 +725,47 @@ class Parser { var lastReadPos = readPos; var lastChar = char; var lastTokens = tokens; - input = s; - readPos = i + 1; - char = -1; - #if hscriptPos - tokens = new List(); - #else - tokens = new haxe.ds.GenericStack(); - #end - var a = new Array(); - var brOpens = 0; + + function reset() + { + input = s; + readPos = i; + char = -1; + #if hscriptPos + tokens = new List(); + #else + tokens = new haxe.ds.GenericStack(); + #end + } + reset(); + var brOpens = -1; while( true ) { var tk = token(); if ( tk == TBrOpen ) + { + if (brOpens == -1) + brOpens = 0; brOpens++; + } else if ( tk == TBrClose ) { + if (brOpens == -1) + unexpected(tk); if (brOpens == 0) break; brOpens--; } if( tk == TEof ) break; + } + var endPos = readPos - 1; + reset(); + input = s.substring(i, endPos) + ";}"; + var a = new Array(); + while( true ) { + var tk = token(); + if (readPos >= endPos) + break; + if( tk == TEof ) break; push(tk); parseFullExpr(a); } From 93b588eb021ae7f1276d905abde72ebdd04990bc Mon Sep 17 00:00:00 2001 From: Davvex87 <102032123+Davvex87@users.noreply.github.com> Date: Sun, 15 Mar 2026 01:43:02 +0000 Subject: [PATCH 03/12] str interp fix from wrong offsets --- hscript/Parser.hx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/hscript/Parser.hx b/hscript/Parser.hx index 7894c58e..61bae1b2 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -751,20 +751,20 @@ class Parser { { if (brOpens == -1) unexpected(tk); - if (brOpens == 0) - break; brOpens--; + if (brOpens <= 0) + break; } - if( tk == TEof ) break; + if( tk == TEof ) + error(EUnterminatedString, currentPos, currentPos); } var endPos = readPos - 1; reset(); input = s.substring(i, endPos) + ";}"; + readPos = 0; var a = new Array(); while( true ) { var tk = token(); - if (readPos >= endPos) - break; if( tk == TEof ) break; push(tk); parseFullExpr(a); @@ -773,7 +773,7 @@ class Parser { ex.push(mk(EBlock(a),0)); nextStr = ""; input = lastInput; - i = readPos; + i = endPos + 1; c = s.charCodeAt(i); readPos = lastReadPos; char = lastChar; From 42249e9f490b9d6a03c430f8b3644577838e77ee Mon Sep 17 00:00:00 2001 From: Davvex87 Date: Sun, 15 Mar 2026 13:58:31 +0000 Subject: [PATCH 04/12] advanced expressions fully work --- hscript/Parser.hx | 53 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/hscript/Parser.hx b/hscript/Parser.hx index 61bae1b2..9abe445c 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -363,9 +363,7 @@ class Parser { { var result = ex[0]; for (i in 1...ex.length) - { result = makeBinop("+", result, ex[i]); - } return parseExprNext(mk(EParent(result), p1)); } } @@ -679,6 +677,12 @@ class Parser { error(ECustom("Invalid string literal"), tokenMin, tokenMax); var nextStr = ""; + function pushNextStr() { + if (nextStr != "") { + ex.push(mk(EConst(CString(nextStr)), tokenMin, tokenMax)); + nextStr = ""; + } + } var i = 0; var c = s.charCodeAt(i); @@ -686,7 +690,7 @@ class Parser { { if (StringTools.isEof(c)) { - ex.push(mk(EConst(CString(nextStr)), tokenMin, tokenMax)); + pushNextStr(); break; } @@ -715,12 +719,14 @@ class Parser { } id += String.fromCharCode(c); } - ex.push(mk(EConst(CString(nextStr)), tokenMin, tokenMax)); + pushNextStr(); ex.push(mk(EIdent(id), tokenMin, tokenMax)); nextStr = ""; } else if (c == "{".code) { + pushNextStr(); + nextStr = ""; var lastInput = input; var lastReadPos = readPos; var lastChar = char; @@ -760,6 +766,7 @@ class Parser { } var endPos = readPos - 1; reset(); + // cool hack to quickly close expressions input = s.substring(i, endPos) + ";}"; readPos = 0; var a = new Array(); @@ -769,7 +776,7 @@ class Parser { push(tk); parseFullExpr(a); } - ex.push(mk(EConst(CString(nextStr)), tokenMin, tokenMax)); + pushNextStr(); ex.push(mk(EBlock(a),0)); nextStr = ""; input = lastInput; @@ -782,7 +789,6 @@ class Parser { else { nextStr += "$"; - i--; } } return ex; @@ -1529,6 +1535,10 @@ class Parser { inline function readChar() { return StringTools.fastCodeAt(input, readPos++); } + + inline function peekChar() { + return StringTools.fastCodeAt(input, readPos); + } function readString( until ) { var c = 0; @@ -1582,7 +1592,36 @@ class Parser { esc = true; else if( c == until ) break; - else { + else if (c == '$'.code && peekChar() == '{'.code) + { + b.addChar('$'.code); + var brOpens = -1; + while (true) + { + c = readChar(); + if (StringTools.isEof(c)) + { + line = old; + error(EUnterminatedString, p1, p1); + break; + } + b.addChar(c); + if ( c == '{'.code ) + { + if (brOpens == -1) + brOpens = 0; + brOpens++; + } + else if ( c == '}'.code ) + { + if (brOpens == -1) + invalidChar(c); + brOpens--; + if (brOpens <= 0) + break; + } + } + } else { if( c == 10 ) line++; b.addChar(c); } From 8bfdcbf8d60df0f1fcbc515a8c207a8e9821fab6 Mon Sep 17 00:00:00 2001 From: Davvex87 Date: Sun, 15 Mar 2026 14:14:40 +0000 Subject: [PATCH 05/12] unit tests for string interpolation --- TestHScript.hx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/TestHScript.hx b/TestHScript.hx index 844eda17..db5efe65 100644 --- a/TestHScript.hx +++ b/TestHScript.hx @@ -252,6 +252,14 @@ class TestHScript extends TestCase { assertScript('var newMap = [{a:"a"}=>"foo", objKey=>"bar"]; newMap[objKey];', 'bar', vars); } + function testStringInterpolation():Void { + assertScript("var a = 5; 'a is ${a}'", 'a is 5'); + assertScript("var a = 5; 'a is ${a + 1}'", 'a is 6'); + assertScript("var a = 5; 'a is ${if (a > 3) \"big\" else \"small\"}'", 'a is big'); + assertScript("var a = 5; 'a is ${switch (a) { case 0: \"zero\"; case 5: \"five\"; default: \"other\"; }}'", 'a is five'); + assertScript("'Hello, ${{var val = false; if (val) \"world\" else {var num = 5 + 3; 'userid_${num}';}}}!'", 'Hello, userid_8!'); + } + static function main() { #if ((haxe_ver < 4) && php) // uncaught exception: The each() function is deprecated. This message will be suppressed on further calls (errno: 8192) From 5d1acc40f5c3d64dc8dff4a7d8acbd26d5f15549 Mon Sep 17 00:00:00 2001 From: Davvex87 <102032123+Davvex87@users.noreply.github.com> Date: Thu, 26 Mar 2026 15:58:31 +0000 Subject: [PATCH 06/12] extract string before passing to makeInterpolatedStr --- hscript/Parser.hx | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/hscript/Parser.hx b/hscript/Parser.hx index 9abe445c..9dc6931e 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -354,9 +354,9 @@ class Parser { e = mk(EIdent(id)); return isBlock(e) ? e : parseExprNext(e); case TConst(c): - if (c.match(CString(_, SingleQuotes))) - { - var ex = makeInterpolatedStr(tk); + switch( c ) { + case CString(s, SingleQuotes): + var ex = makeInterpolatedStr(s); if (ex.length == 1) return parseExprNext(ex[0]); else @@ -366,8 +366,9 @@ class Parser { result = makeBinop("+", result, ex[i]); return parseExprNext(mk(EParent(result), p1)); } + default: + return parseExprNext(mk(EConst(c))); } - return parseExprNext(mk(EConst(c))); case TPOpen: tk = token(); if( tk == TPClose ) { @@ -657,24 +658,8 @@ class Parser { } } - function makeInterpolatedStr(tk:Token):Array { + function makeInterpolatedStr(s:String):Array { var ex = new Array(); - var s = switch(tk) - { - case TConst(c): - switch(c) - { - case CString(s): - s; - case _: - null; - } - case _: - null; - } - - if (s == null) - error(ECustom("Invalid string literal"), tokenMin, tokenMax); var nextStr = ""; function pushNextStr() { From fdd13ab4e3ad8079ea82b02bbd4bb61092dd9297 Mon Sep 17 00:00:00 2001 From: Davvex87 <102032123+Davvex87@users.noreply.github.com> Date: Thu, 26 Mar 2026 16:01:46 +0000 Subject: [PATCH 07/12] StringTools.fastCodeAt instead of charCodeAt --- hscript/Parser.hx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/hscript/Parser.hx b/hscript/Parser.hx index 9dc6931e..9d6a09cb 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -670,7 +670,7 @@ class Parser { } var i = 0; - var c = s.charCodeAt(i); + var c = StringTools.fastCodeAt(s, i); while (true) { if (StringTools.isEof(c)) @@ -682,22 +682,22 @@ class Parser { if (c != "$".code) { nextStr += String.fromCharCode(c); - c = s.charCodeAt(++i); + c = StringTools.fastCodeAt(s, ++i); continue; } - c = s.charCodeAt(++i); + c = StringTools.fastCodeAt(s, ++i); if (c >= 48 && c <= 57 || c == "$".code) { nextStr += "$" + String.fromCharCode(c); - c = s.charCodeAt(++i); + c = StringTools.fastCodeAt(s, ++i); continue; } if( idents[c] ) { var id = String.fromCharCode(c); while( true ) { - c = s.charCodeAt(++i); + c = StringTools.fastCodeAt(s, ++i); if( StringTools.isEof(c) ) c = 0; if( !idents[c] ) { break; @@ -766,7 +766,7 @@ class Parser { nextStr = ""; input = lastInput; i = endPos + 1; - c = s.charCodeAt(i); + c = StringTools.fastCodeAt(s, i); readPos = lastReadPos; char = lastChar; tokens = lastTokens; From 2f159b996f85ea28cf62022e18e3eb79fa3e62d0 Mon Sep 17 00:00:00 2001 From: Davvex87 <102032123+Davvex87@users.noreply.github.com> Date: Thu, 26 Mar 2026 16:05:24 +0000 Subject: [PATCH 08/12] use StringBuf instead of adding characters to string --- hscript/Parser.hx | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/hscript/Parser.hx b/hscript/Parser.hx index 9d6a09cb..61a5e852 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -661,11 +661,14 @@ class Parser { function makeInterpolatedStr(s:String):Array { var ex = new Array(); - var nextStr = ""; + inline function newBuf() + return new StringBuf(); + + var nextStr = newBuf(); function pushNextStr() { - if (nextStr != "") { - ex.push(mk(EConst(CString(nextStr)), tokenMin, tokenMax)); - nextStr = ""; + if (nextStr.length > 0) { + ex.push(mk(EConst(CString(nextStr.toString())), tokenMin, tokenMax)); + nextStr = newBuf(); } } @@ -681,7 +684,7 @@ class Parser { if (c != "$".code) { - nextStr += String.fromCharCode(c); + nextStr.addChar(c); c = StringTools.fastCodeAt(s, ++i); continue; } @@ -689,7 +692,8 @@ class Parser { c = StringTools.fastCodeAt(s, ++i); if (c >= 48 && c <= 57 || c == "$".code) { - nextStr += "$" + String.fromCharCode(c); + nextStr.add("$"); + nextStr.addChar(c); c = StringTools.fastCodeAt(s, ++i); continue; } @@ -706,12 +710,12 @@ class Parser { } pushNextStr(); ex.push(mk(EIdent(id), tokenMin, tokenMax)); - nextStr = ""; + nextStr = newBuf(); } else if (c == "{".code) { pushNextStr(); - nextStr = ""; + nextStr = newBuf(); var lastInput = input; var lastReadPos = readPos; var lastChar = char; @@ -763,7 +767,7 @@ class Parser { } pushNextStr(); ex.push(mk(EBlock(a),0)); - nextStr = ""; + nextStr = newBuf(); input = lastInput; i = endPos + 1; c = StringTools.fastCodeAt(s, i); @@ -773,7 +777,7 @@ class Parser { } else { - nextStr += "$"; + nextStr.add("$"); } } return ex; From dc624b94554ff182e426b6ef4e3a4f5093e0b922 Mon Sep 17 00:00:00 2001 From: Davvex87 <102032123+Davvex87@users.noreply.github.com> Date: Thu, 26 Mar 2026 16:07:58 +0000 Subject: [PATCH 09/12] char check fixes --- hscript/Parser.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hscript/Parser.hx b/hscript/Parser.hx index 61a5e852..97e79deb 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -690,7 +690,7 @@ class Parser { } c = StringTools.fastCodeAt(s, ++i); - if (c >= 48 && c <= 57 || c == "$".code) + if ((c >= "0".code && c <= "9".code) || c == "$".code) { nextStr.add("$"); nextStr.addChar(c); From 89ce448416f95cd9753753d83d8787b0d20c204d Mon Sep 17 00:00:00 2001 From: Davvex87 <102032123+Davvex87@users.noreply.github.com> Date: Thu, 26 Mar 2026 16:10:05 +0000 Subject: [PATCH 10/12] Better str interpolation detection for blocks inside string --- hscript/Parser.hx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hscript/Parser.hx b/hscript/Parser.hx index 97e79deb..4f6fed66 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -755,8 +755,7 @@ class Parser { } var endPos = readPos - 1; reset(); - // cool hack to quickly close expressions - input = s.substring(i, endPos) + ";}"; + input = s.substring(++i, endPos); readPos = 0; var a = new Array(); while( true ) { From 4f4cebdadd0e578ab730d4448ef9a2d52bf6aacb Mon Sep 17 00:00:00 2001 From: Davvex87 <102032123+Davvex87@users.noreply.github.com> Date: Thu, 26 Mar 2026 16:14:35 +0000 Subject: [PATCH 11/12] fix for double quote strings incorrectly doing string interpolation, even though they shouldn't --- hscript/Parser.hx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hscript/Parser.hx b/hscript/Parser.hx index 4f6fed66..92a243c8 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -1528,7 +1528,7 @@ class Parser { return StringTools.fastCodeAt(input, readPos); } - function readString( until ) { + function readString( until , allowInterpolation = false ) { var c = 0; var b = new StringBuf(); var esc = false; @@ -1580,7 +1580,7 @@ class Parser { esc = true; else if( c == until ) break; - else if (c == '$'.code && peekChar() == '{'.code) + else if (allowInterpolation && c == '$'.code && peekChar() == '{'.code) { b.addChar('$'.code); var brOpens = -1; @@ -1760,8 +1760,8 @@ class Parser { case "}".code: return TBrClose; case "[".code: return TBkOpen; case "]".code: return TBkClose; - case "'".code: return TConst( CString(readString(char), SingleQuotes) ); - case '"'.code: return TConst( CString(readString(char), DoubleQuotes) ); + case "'".code: return TConst( CString(readString(char, true), SingleQuotes) ); + case '"'.code: return TConst( CString(readString(char, false), DoubleQuotes) ); case "?".code: char = readChar(); if( char == ".".code ) From 066be2212fdaf4acc27e42347898bd3c0c746a8c Mon Sep 17 00:00:00 2001 From: Davvex87 <102032123+Davvex87@users.noreply.github.com> Date: Thu, 26 Mar 2026 16:30:41 +0000 Subject: [PATCH 12/12] fix for -D hscriptPos not working on interpolated strings --- hscript/Parser.hx | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/hscript/Parser.hx b/hscript/Parser.hx index 92a243c8..11f8a5a6 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -720,14 +720,25 @@ class Parser { var lastReadPos = readPos; var lastChar = char; var lastTokens = tokens; + var lastOffset = offset; + var lastLine = line; + #if hscriptPos + var lastTokenMin = tokenMin; + var lastTokenMax = tokenMax; + var lastOldTokenMin = oldTokenMin; + var lastOldTokenMax = oldTokenMax; + #end function reset() { input = s; readPos = i; char = -1; + offset = lastOffset; #if hscriptPos tokens = new List(); + tokenMin = oldTokenMin = currentPos; + tokenMax = oldTokenMax = currentPos; #else tokens = new haxe.ds.GenericStack(); #end @@ -755,8 +766,14 @@ class Parser { } var endPos = readPos - 1; reset(); - input = s.substring(++i, endPos); + var startPos = ++i; + input = s.substring(startPos, endPos); readPos = 0; + offset = lastOffset + startPos; + #if hscriptPos + tokenMin = oldTokenMin = currentPos; + tokenMax = oldTokenMax = currentPos; + #end var a = new Array(); while( true ) { var tk = token(); @@ -773,6 +790,14 @@ class Parser { readPos = lastReadPos; char = lastChar; tokens = lastTokens; + offset = lastOffset; + line = lastLine; + #if hscriptPos + tokenMin = lastTokenMin; + tokenMax = lastTokenMax; + oldTokenMin = lastOldTokenMin; + oldTokenMax = lastOldTokenMax; + #end } else {