Compare commits

...
Author SHA1 Message Date
Jaime Humberto Macias Bustamante 9fa4a548e5 String Interpolation Support
implementation from hscript-iris (by inky03)

Note: it needs optimization
2025-09-06 19:39:57 -07:00
Jaime Humberto Macias Bustamante 18f4ebccd9 Fix small bug with slash literal ("\/") 2025-09-06 19:31:48 -07:00
Jaime Humberto Macias Bustamante 8dcebe3930 Regular Expression support (regex) 2025-09-06 19:31:48 -07:00
5 changed files with 205 additions and 9 deletions
+2
View File
@@ -134,6 +134,8 @@ class Bytes {
// TODO
case ECast(e, _):
// TODO
case ERegex(e, f):
// TODO
case EConst(c):
doEncodeConst(c);
case EIdent(v):
+2 -1
View File
@@ -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
@@ -84,6 +84,7 @@ enum Expr {
EClass( name:String, fields:Array<Expr>, ?extend:String, interfaces:Array<String>, ?isFinal:Bool, ?isPrivate:Bool );
EEnum( en:EnumDecl, ?isAbstract:Bool );
ECast(e:Expr, ?t:CType);
ERegex(e:String, flags:String);
}
@:structInit
+2
View File
@@ -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;
+196 -8
View File
@@ -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 = "+*/-=!><&|^%~";
@@ -332,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:
@@ -369,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();
@@ -513,6 +526,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);
}
@@ -1628,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<Expr> = [];
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<Expr> = 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
@@ -1863,8 +1966,9 @@ class Parser {
return StringTools.fastCodeAt(input, readPos++);
}
function readString( until:Int ):String {
function readString( until:Int, regex:Bool = false, interpolate:Bool = false ):String {
var c = 0;
var prev = 0;
var b = new StringBuf();
var esc = false;
var old = line;
@@ -1911,18 +2015,93 @@ class Parser {
b.addChar(k);
default: invalidChar(c);
}
} else if( c == 92 )
} 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 == 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);
if(regex)
prev = c;
}
}
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<haxe.PosInfos>) {
//function ttrace(v:Dynamic, ?infos : Null<haxe.PosInfos>) {
@@ -2090,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) {
@@ -2146,7 +2328,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 +2338,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);
@@ -2346,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
}
}
@@ -2370,6 +2557,7 @@ class Parser {
case TDoubleDot: ":";
case TMeta(id): "@" + id;
case TPrepro(id): "#" + id;
case TRegex(e, f): '~/$e/$f';
}
}
+3
View File
@@ -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);