From 8dcebe3930809c0c1404fa37fceb5f48e5e0a6ee Mon Sep 17 00:00:00 2001 From: Jaime Humberto Macias Bustamante Date: Wed, 3 Sep 2025 20:13:39 -0700 Subject: [PATCH 1/3] Regular Expression support (regex) --- hscript/Bytes.hx | 2 ++ hscript/Expr.hx | 1 + hscript/Interp.hx | 2 ++ hscript/Parser.hx | 55 +++++++++++++++++++++++++++++++++++++++++++--- hscript/Printer.hx | 3 +++ 5 files changed, 60 insertions(+), 3 deletions(-) diff --git a/hscript/Bytes.hx b/hscript/Bytes.hx index 20619bf..b423d0e 100644 --- a/hscript/Bytes.hx +++ b/hscript/Bytes.hx @@ -134,6 +134,8 @@ class Bytes { // TODO case ECast(e, _): // TODO + case ERegex(e, f): + // TODO case EConst(c): doEncodeConst(c); case EIdent(v): diff --git a/hscript/Expr.hx b/hscript/Expr.hx index f313d19..3733594 100644 --- a/hscript/Expr.hx +++ b/hscript/Expr.hx @@ -84,6 +84,7 @@ enum Expr { EClass( name:String, fields:Array, ?extend:String, interfaces:Array, ?isFinal:Bool, ?isPrivate:Bool ); EEnum( en:EnumDecl, ?isAbstract:Bool ); ECast(e:Expr, ?t:CType); + ERegex(e:String, flags:String); } @:structInit diff --git a/hscript/Interp.hx b/hscript/Interp.hx index b548f83..6b4ca96 100644 --- a/hscript/Interp.hx +++ b/hscript/Interp.hx @@ -885,6 +885,8 @@ class Interp { variables.set(en.name, enumThingy); case ECast(e, _): // TODO return expr(e); + case ERegex(e, f): + return new EReg(e, f); case EConst(c): switch (c) { case CInt(v): return v; diff --git a/hscript/Parser.hx b/hscript/Parser.hx index ff75805..38ab0c1 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -43,6 +43,7 @@ enum Token { TDoubleDot; TMeta( s : String ); TPrepro( s : String ); + TRegex( e : String, f:String ); } @:structInit @@ -72,6 +73,11 @@ class Parser { **/ public var allowJSON : Bool; + /** + activate Regular Expression parsing + **/ + public var allowRegex : Bool; + /** allow types declarations **/ @@ -115,6 +121,8 @@ class Parser { #end + static inline var regexFlags: String = "igmsu"; + public function new() { line = 1; opChars = "+*/-=!><&|^%~"; @@ -513,6 +521,8 @@ class Parser { var e = parseExpr(); isVar = false; return mk(EMeta(id, args, e),p1); + case TRegex(e, f) if(allowRegex): + return mk(ERegex(e, f), p1); default: return unexpected(tk); } @@ -1863,7 +1873,7 @@ class Parser { return StringTools.fastCodeAt(input, readPos++); } - function readString( until:Int ):String { + function readString( until:Int, regex:Bool = false ):String { var c = 0; var b = new StringBuf(); var esc = false; @@ -1911,7 +1921,7 @@ class Parser { b.addChar(k); default: invalidChar(c); } - } else if( c == 92 ) + } else if( c == 92 && !regex) esc = true; else if( c == until ) break; @@ -1923,6 +1933,39 @@ class Parser { return b.toString(); } + function readFlags():String { + if(!allowRegex) return null; + var c = 0; + var b = new StringBuf(); + 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; + } + // semicolon + if(c == 59) { + this.char = c; + break; + } + + var f = String.fromCharCode(c); + if(regexFlags.indexOf(f) != -1) + b.addChar(c); + else + invalidChar(c); + } + + return b.toString(); + } + function token():Token { //function token(?infos : Null) { //function ttrace(v:Dynamic, ?infos : Null) { @@ -2146,7 +2189,7 @@ class Parser { invalidChar(char); default: if( ops[char] ) { - var op = String.fromCharCode(char); + var op:String = String.fromCharCode(char); while( true ) { char = readChar(); if( StringTools.isEof(char) ) char = 0; @@ -2156,6 +2199,11 @@ class Parser { } var pop = op; op += String.fromCharCode(char); + if( allowRegex && op == "~/") { + var e = readString(char, true); + var f = readFlags(); + return TRegex(e, f); + } if( !opPriority.exists(op) && opPriority.exists(pop) ) { if( op == "//" || op == "/*" ) return tokenComment(op,char); @@ -2370,6 +2418,7 @@ class Parser { case TDoubleDot: ":"; case TMeta(id): "@" + id; case TPrepro(id): "#" + id; + case TRegex(e, f): '~/$e/${f == null ? '' : f}'; } } diff --git a/hscript/Printer.hx b/hscript/Printer.hx index b671779..34fb765 100644 --- a/hscript/Printer.hx +++ b/hscript/Printer.hx @@ -186,6 +186,9 @@ class Printer { addType(t); add(")"); } + case ERegex(e, f): + add('~/$e/$f'); + add(';\n'); case EConst(c): switch( c ) { case CInt(i): add(i); From 18f4ebccd981d2b768aff92d2907cbab212c0773 Mon Sep 17 00:00:00 2001 From: Jaime Humberto Macias Bustamante Date: Wed, 3 Sep 2025 22:00:32 -0700 Subject: [PATCH 2/3] Fix small bug with slash literal ("\/") --- hscript/Parser.hx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/hscript/Parser.hx b/hscript/Parser.hx index 38ab0c1..8493f5f 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -1875,6 +1875,7 @@ class Parser { function readString( until:Int, regex:Bool = false ):String { var c = 0; + var prev = 0; var b = new StringBuf(); var esc = false; var old = line; @@ -1923,11 +1924,19 @@ class Parser { } } else if( c == 92 && !regex) esc = true; - else if( c == until ) - break; + else if( c == until ) { + if(regex && prev == 92) { + b.addChar(c); + prev = c; + } + else + break; + } else { if( c == 10 ) line++; b.addChar(c); + if(regex) + prev = c; } } return b.toString(); From 9fa4a548e5cbaf86548dcece2901f2d0b4af7984 Mon Sep 17 00:00:00 2001 From: Jaime Humberto Macias Bustamante Date: Sat, 6 Sep 2025 19:39:57 -0700 Subject: [PATCH 3/3] String Interpolation Support implementation from hscript-iris (by inky03) Note: it needs optimization --- hscript/Expr.hx | 2 +- hscript/Parser.hx | 140 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 136 insertions(+), 6 deletions(-) diff --git a/hscript/Expr.hx b/hscript/Expr.hx index 3733594..d84f57d 100644 --- a/hscript/Expr.hx +++ b/hscript/Expr.hx @@ -34,7 +34,7 @@ typedef UInt64 = #if cpp cpp.UInt64 #else Int #end; enum Const { CInt( v : Int ); CFloat( f : Float ); - CString( s : String ); + CString( s : String, ?i : Bool ); } #if hscriptPos diff --git a/hscript/Parser.hx b/hscript/Parser.hx index 8493f5f..4552d30 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -340,7 +340,7 @@ class Parser { if( !allowJSON ) unexpected(tk); switch( c ) { - case CString(s): id = s; + case CString(s, _): id = s; default: unexpected(tk); } case TBrClose: @@ -377,6 +377,11 @@ class Parser { e = mk(EIdent(id)); return parseExprNext(e); case TConst(c): + switch(c) { + case CString(s, i): + if(i) return parseExprNext(interpolateString(s)); + default: + } return parseExprNext(mk(EConst(c))); case TPOpen: tk = token(); @@ -1638,6 +1643,94 @@ class Parser { } return args; } + + // From hscript-iris: https://github.com/pisayesiwsi/hscript-iris/blob/dev/crowplexus/hscript/Parser.hx#L409 + // by inky03 + function interpolateString(s:String):Expr { + var exprs:Array = []; + var dollarPos:Int = s.indexOf('$'); + + if(dollarPos == -1) + return mk(EConst(CString(s))); + + final singleFirst:EReg = ~/[a-zA-Z_]/i; + final singleExpr:EReg = ~/[a-zA-Z0-9_]/i; + // TODO: optimize this using regex + while(dollarPos > -1) { + var pos:Int = dollarPos; + var pre:String = s.substr(0, pos); + var next:String = s.charAt(++pos); + if(next == '{') { + if (pre != '') + exprs.push(mk(EConst(CString(pre)))); + var exprStr:String = ''; + var depth:Int = 1; + while (true) { + next = s.charAt(++pos); + if (next == '{') { + depth++; + } else if (next == '}') { + depth--; + } + if (depth < 1) + break; + if (pos >= s.length) { + error(EUnterminatedString, pos, pos); + } + exprStr += next; + } + if (exprStr.trim() == '') { + error(ECustom("Expression cannot be empty"), pos, pos); + } + var prevChar = char; + var prevInput = input; + var prevReadPos = readPos; // a bit stupid innit??? + #if hscriptPos + var prevOrigin = origin; + #end + var expr = parseString('($exprStr)' #if hscriptPos, origin #end); + readPos = prevReadPos; // rolling back parser state because otherwise we get problems... + input = prevInput; + char = prevChar; + #if hscriptPos + origin = prevOrigin; + #end + exprs.push(expr); + pos++; + } + else if(singleFirst.match(next)) { + if (pre != '') + exprs.push(mk(EConst(CString(pre)))); + var ident: String = ''; + while(singleExpr.match(next)) { + ident += next; + next = s.charAt(++pos); + } + exprs.push(mk(EIdent(ident))); + } + else if (next == '$') { + var secondToNext: String = s.charAt(pos); + if (secondToNext == "$") { // if its another dollar, skip... + s = pre + s.substr(pos, pos + 1); // remove $ ahead of the current one + break; + } + exprs.push(mk(EConst(CString(pre + '$')))); + } + s = s.substr(pos++); + dollarPos = s.indexOf('$'); + } + if (exprs.length == 0) { + return mk(EConst(CString(s))); + } else { + exprs.push(mk(EConst(CString(s)))); + var expr:Null = exprs.shift(); + while(true) { + if(exprs.length == 0) break; + expr = mk(EBinop('+', expr, exprs.shift())); + } + return expr; + } + } function checkAccess(get:FieldPropertyAccess, set:FieldPropertyAccess, ?expr:Expr, ?type:CType) { #if hscriptPos @@ -1873,7 +1966,7 @@ class Parser { return StringTools.fastCodeAt(input, readPos++); } - function readString( until:Int, regex:Bool = false ):String { + function readString( until:Int, regex:Bool = false, interpolate:Bool = false ):String { var c = 0; var prev = 0; var b = new StringBuf(); @@ -1932,6 +2025,40 @@ class Parser { else break; } + else if (c == 36 && interpolate) { // brace for impact !! + // From hscript-iris: https://github.com/pisayesiwsi/hscript-iris/blob/dev/crowplexus/hscript/Parser.hx#L1665 + // by inky03 + // TODO: optimize this + b.addChar(c); + var next = readChar(); + if (next == 123) { + b.addChar(next); + var depth:Int = 0; + while (true) { + next = readChar(); + if (StringTools.isEof(next)) { + error(EUnterminatedString, p1, p1); + } + b.addChar(next); + if (next == "'".code) { + var nextStr:String = readString("'".code, false, true); + for (char in nextStr) { + b.addChar(char); + } + b.addChar("'".code); + next = readChar(); + b.addChar(next); + } + if (next == 125) { + depth--; + if (depth < 0) + break; + } + } + } else { + readPos--; + } + } else { if( c == 10 ) line++; b.addChar(c); @@ -2142,7 +2269,10 @@ 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, false, true), true) ); + case '"'.code: + return TConst( CString(readString(char), false) ); case "?".code: char = readChar(); switch (char) { @@ -2403,7 +2533,7 @@ class Parser { return switch(c) { case CInt(v): Std.string(v); case CFloat(f): Std.string(f); - case CString(s): s; // TODO : escape + quote + case CString(s, _): s; // TODO : escape + quote } } @@ -2427,7 +2557,7 @@ class Parser { case TDoubleDot: ":"; case TMeta(id): "@" + id; case TPrepro(id): "#" + id; - case TRegex(e, f): '~/$e/${f == null ? '' : f}'; + case TRegex(e, f): '~/$e/$f'; } }