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 1/5] 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 2/5] 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 3/5] 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 4/5] 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 5/5] 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)