From fb8f35e2a561eff125bcf38c6c2e829da8858c3d Mon Sep 17 00:00:00 2001 From: NeeEoo Date: Thu, 25 Apr 2024 17:23:35 +0200 Subject: [PATCH] Added String.fromCharCode, "a".code Added a preprocessor --- hscript/Error.hx | 67 +++++++++++++++++++++++++++++ hscript/Expr.hx | 38 ---------------- hscript/Interp.hx | 7 ++- hscript/Parser.hx | 12 ++++++ hscript/Preprocessor.hx | 58 +++++++++++++++++++++++++ hscript/Printer.hx | 11 +++-- hscript/Tools.hx | 8 ++++ tests/src/HScriptRunner.hx | 20 +++++++++ tests/src/Util.hx | 81 ++++++++++++++++------------------- tests/src/tests/StringCase.hx | 59 ++++++++++++++----------- tests/src/tests/TestCase.hx | 27 ++++++++++++ 11 files changed, 277 insertions(+), 111 deletions(-) create mode 100644 hscript/Error.hx create mode 100644 hscript/Preprocessor.hx diff --git a/hscript/Error.hx b/hscript/Error.hx new file mode 100644 index 0000000..d01f2fa --- /dev/null +++ b/hscript/Error.hx @@ -0,0 +1,67 @@ +/* + * 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 hscript; + +#if hscriptPos +class Error { + public var e : ErrorDef; + public var pmin : Int; + public var pmax : Int; + public var origin : String; + public var line : Int; + public function new(e, pmin, pmax, origin, line) { + this.e = e; + this.pmin = pmin; + this.pmax = pmax; + this.origin = origin; + this.line = line; + } + public function toString(): String { + return Printer.errorToString(this); + } +} +enum ErrorDef +#else +enum Error +#end +{ + EInvalidChar( c : Int ); + EUnexpected( s : String ); + EUnterminatedString; + EUnterminatedComment; + EInvalidPreprocessor( msg : String ); + EUnknownVariable( v : String ); + EInvalidIterator( v : String ); + EInvalidOp( op : String ); + EInvalidAccess( f : String ); + ECustom( msg : String ); + EPreset( msg : ErrorMessage ); + EInvalidClass( className : String); + EAlreadyExistingClass( className : String); + EInvalidEscape( s : String ); +} + +enum abstract ErrorMessage(String) to String { + final INVALID_CHAR_CODE_MULTI = "'char'.code only works on single characters"; + final FROM_CHAR_CODE_NON_INT = "String.fromCharCode only works on integers"; + final EMPTY_INTERPOLATION = "Invalid interpolation: Expression cannot be empty"; +} \ No newline at end of file diff --git a/hscript/Expr.hx b/hscript/Expr.hx index bb51ec1..c033a31 100644 --- a/hscript/Expr.hx +++ b/hscript/Expr.hx @@ -139,44 +139,6 @@ enum CType { CTNamed( n : String, t : CType ); } -#if hscriptPos -class Error { - public var e : ErrorDef; - public var pmin : Int; - public var pmax : Int; - public var origin : String; - public var line : Int; - public function new(e, pmin, pmax, origin, line) { - this.e = e; - this.pmin = pmin; - this.pmax = pmax; - this.origin = origin; - this.line = line; - } - public function toString(): String { - return Printer.errorToString(this); - } -} -enum ErrorDef -#else -enum Error -#end -{ - EInvalidChar( c : Int ); - EUnexpected( s : String ); - EUnterminatedString; - EUnterminatedComment; - EInvalidPreprocessor( msg : String ); - EUnknownVariable( v : String ); - EInvalidIterator( v : String ); - EInvalidOp( op : String ); - EInvalidAccess( f : String ); - ECustom( msg : String ); - EInvalidClass( className : String); - EAlreadyExistingClass( className : String); - EInvalidEscape( s : String ); -} - enum ModuleDecl { DPackage( path : Array ); DImport( path : Array, ?everything : Bool ); diff --git a/hscript/Interp.hx b/hscript/Interp.hx index 704eb2d..e8bb265 100644 --- a/hscript/Interp.hx +++ b/hscript/Interp.hx @@ -158,6 +158,11 @@ class Interp { variables.set("null", null); variables.set("true", true); variables.set("false", false); + #if !NO_FROM_CHAR_CODE_FIX + variables.set("__StringWorkaround__fromCharCode", function(a:Int) { // TODO: make hscript only add this if its used + return String.fromCharCode(a); + }); + #end variables.set("trace", Reflect.makeVarArgs(function(el) { var inf = posInfos(); var v = el.shift(); @@ -472,7 +477,7 @@ class Interp { } } - public inline function error(e:#if hscriptPos ErrorDef #else Error #end, rethrow = false):Dynamic { + public inline function error(e:#if hscriptPos Error.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) { diff --git a/hscript/Parser.hx b/hscript/Parser.hx index 0099e88..c9ee384 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -163,6 +163,14 @@ class Parser { opPriority.set(x, x == "++" || x == "--" ? -1 : -2); } + public static inline function getBaseError( err ):Error { + #if hscriptPos + return new Error(err, 0, 0, "", 0); + #else + return err; + #end + } + public inline function error( err, pmin, pmax ) { if( !resumeErrors ) #if hscriptPos @@ -215,6 +223,7 @@ class Parser { parseFullExpr(a); } var expr = if( a.length == 1 ) a[0] else mk(EBlock(a),0); + expr = Preprocessor.process(expr); if(Parser.optimize) { expr = Optimizer.optimize(expr); var printer = new Printer(); @@ -1880,6 +1889,9 @@ class Parser { interpString.push(arr); interpolation = false; + if(arr.length == 2) // If its just () + error(EPreset(EMPTY_INTERPOLATION), p1, p1); + if( StringTools.isEof(peekChar()) ) break; diff --git a/hscript/Preprocessor.hx b/hscript/Preprocessor.hx new file mode 100644 index 0000000..5035c18 --- /dev/null +++ b/hscript/Preprocessor.hx @@ -0,0 +1,58 @@ +package hscript; + +import hscript.Expr; +import hscript.Error; +import hscript.Tools; +import hscript.Parser; + +@:access(hscript.Parser) +class Preprocessor { + static inline function expr(e:Expr) return Tools.expr(e); + + /** + * Preprocesses the expression, like 'a'.code => 97 + * Also for transforming any abstracts into their implementations (TODO) + * Also for transforming any static extensions into their real form (TODO) + **/ + public static function process(e:Expr, doBlock:Bool = true):Expr { + if(e == null) + return null; + + trace(expr(e)); + + e = Tools.map(e, function(e) { + return process(e); + }); + + switch(expr(e)) { + case EField(expr(_) => EConst(CString(s)), "code", _): // Transform string.code into charCode + if(s.length != 1) { + throw Parser.getBaseError(EPreset(INVALID_CHAR_CODE_MULTI)); + } + return mk(EConst(CInt(s.charCodeAt(0))), e); + case ECall(expr(_) => EField(expr(_) => EIdent("String"), "fromCharCode", _), [e]): + switch(expr(e)) { + case EConst(CInt(i)): + return mk(EConst(CString(String.fromCharCode(i))), e); + default: + // __StringWorkaround__fromCharCode(i); + #if !NO_FROM_CHAR_CODE_FIX + return mk(ECall(mk(EIdent("__StringWorkaround__fromCharCode"), e), [e]), e); + #else + throw Parser.getBaseError(EPreset(FROM_CHAR_CODE_NON_INT)); + #end + } + default: + } + + return e; + } + + static function mk(e:ExprDef, s:Expr):Expr { + #if hscriptPos + return new Expr(e, s.pmin, s.pmax, s.origin, s.line); + #else + return e; + #end + } +} \ No newline at end of file diff --git a/hscript/Printer.hx b/hscript/Printer.hx index cb209fa..4a0e293 100644 --- a/hscript/Printer.hx +++ b/hscript/Printer.hx @@ -513,12 +513,16 @@ class Printer { } } - public static function toString( e : Expr ) { + public inline static function toString( e : Expr ) { return new Printer().exprToString(e); } - public static function errorToString( e : Expr.Error ) { - var message = switch( #if hscriptPos e.e #else e #end ) { + public inline static function compareErrors( e1 : Error, e2 : Error ) { + return Type.enumEq(Tools.cleanError(e1), Tools.cleanError(e2)); + } + + public static function errorToString( e : Error ) { + var message = switch( Tools.cleanError(e) ) { case EInvalidChar(c): "Invalid character: '"+(StringTools.isEof(c) ? "EOF (End Of File)" : String.fromCharCode(c))+"' ("+c+")"; case EUnexpected(s): "Unexpected token: \""+s+"\""; case EUnterminatedString: "Unterminated string"; @@ -529,6 +533,7 @@ class Printer { case EInvalidOp(op): "Invalid operator: "+op; case EInvalidAccess(f): "Invalid access to field " + f; case ECustom(msg): msg; + case EPreset(msg): msg; case EInvalidClass(cla): "Invalid class: " + cla + " was not found."; case EAlreadyExistingClass(cla): 'Custom Class named $cla already exists.'; case EInvalidEscape(s): "Invalid escape sequence: " + s; diff --git a/hscript/Tools.hx b/hscript/Tools.hx index 5e30f1d..ea293e4 100644 --- a/hscript/Tools.hx +++ b/hscript/Tools.hx @@ -107,6 +107,14 @@ class Tools { #end } + public static inline function cleanError( e : Error ) { + #if hscriptPos + return e.e; + #else + return e; + #end + } + public static inline function mk( e : ExprDef, p : Expr ):Expr { #if hscriptPos return new Expr(e, p.pmin, p.pmax, p.origin, p.line); diff --git a/tests/src/HScriptRunner.hx b/tests/src/HScriptRunner.hx index 32211ee..67f5e9c 100644 --- a/tests/src/HScriptRunner.hx +++ b/tests/src/HScriptRunner.hx @@ -51,4 +51,24 @@ class HScriptRunner { else return interp.exprReturn(lastExpr); } + + public function executeUnsafe(script:String):Dynamic { + var interp = clearPrevious ? getNewInterp() : this.interp; + lastExpr = Util.parseUnsafe(headerCode + script + tailCode); + if (clearPrevious) + return interp.execute(lastExpr); + else + return interp.exprReturn(lastExpr); + } + + public function executeWithVarsUnsafe(script:String, vars:Dynamic):Dynamic { + var interp = clearPrevious ? getNewInterp() : this.interp; + lastExpr = Util.parseUnsafe(headerCode + script + tailCode); + for(v in Reflect.fields(vars)) + interp.variables.set(v, Reflect.field(vars, v)); + if (clearPrevious) + return interp.execute(lastExpr); + else + return interp.exprReturn(lastExpr); + } } \ No newline at end of file diff --git a/tests/src/Util.hx b/tests/src/Util.hx index 52e314a..095a546 100644 --- a/tests/src/Util.hx +++ b/tests/src/Util.hx @@ -1,4 +1,4 @@ -import hscript.Expr.Error; +import hscript.Error; import hscript.Printer; import haxe.Constraints.IMap; import haxe.EnumTools.EnumValueTools; @@ -97,85 +97,65 @@ class Util { return true; } + public static inline function passed() { + passedTestUnits++; + return true; + } + + public static inline function failed() { + failedTestUnits++; + return false; + } + public static function assert(value:Bool, message:String, ?pos:haxe.PosInfos) { if (value) { - passedTestUnits++; - return true; + return passed(); } else { Sys.println("Assertion failed: " + message); Sys.println("> At " + pos.fileName + ":" + pos.lineNumber); - failedTestUnits++; - return false; + return failed(); } } public static function assertEq(value:Dynamic, expected:Dynamic, message:String, ?pos:haxe.PosInfos) { var equals = deepEqual(value, expected); - var passed = equals; - //if (Std.isOfType(value, Array) && Std.isOfType(expected, Array)) { - // if (deepCompareArrays(value, expected)) - // passed = true; - //} - //else if (Std.isOfType(value, Enum) && Std.isOfType(expected, Enum)) { - // if(Type.enumEq(value, expected)) - // passed = true; - //} + var _passed = equals; - if (passed) { - passedTestUnits++; - return true; + if (_passed) { + return passed(); } else { Sys.println("Assertion failed: " + message + " Expected: " + expected + " Got: " + value); Sys.println("> At " + pos.fileName + ":" + pos.lineNumber); - failedTestUnits++; - return false; + return failed(); } } public static function assertEqPrintable(value:Dynamic, expected:Dynamic, message:String, ?pos:haxe.PosInfos) { - //var passed = value == expected; - //if (Std.isOfType(value, Array) && Std.isOfType(expected, Array)) { - // if (deepCompareArrays(value, expected)) - // passed = true; - //} - //else if (Std.isOfType(value, Enum) && Std.isOfType(expected, Enum)) { - // if(Type.enumEq(value, expected)) - // passed = true; - //} var equals = deepEqual(value, expected); - var passed = equals; + var _passed = equals; - if (passed) { - passedTestUnits++; - return true; + if (_passed) { + return passed(); } else { Sys.println("Assertion failed: " + message + " Expected: " + Printer.getEscapedString(expected) + " Got: " + Printer.getEscapedString(value)); Sys.println("> At " + pos.fileName + ":" + pos.lineNumber); - failedTestUnits++; - return false; + return failed(); } } public static function assertNeq(value:Dynamic, expected:Dynamic, message:String, ?pos:haxe.PosInfos) { - //var passed = value != expected; - //if (Std.isOfType(value, Array) && Std.isOfType(expected, Array)) { - // if (!deepCompareArrays(value, expected)) - // passed = true; - //} var equals = deepEqual(value, expected); - var passed = !equals; + var _passed = !equals; // WARNING THIS MIGHT NOT WORK - if (passed) { - passedTestUnits++; - return true; + if (_passed) { + return passed(); } else { Sys.println("Assertion failed: " + message + " Expected: " + expected + " Got: " + value); Sys.println("> At " + pos.fileName + ":" + pos.lineNumber); - failedTestUnits++; - return false; + return failed(); } } @@ -234,6 +214,17 @@ class Util { return result; } + /** + * Parses a string without checking for errors + */ + public static function parseUnsafe(str:String) { + var p = new Parser(); + p.allowTypes = true; + p.allowMetadata = true; + p.allowJSON = true; + return p.parseString(str); + } + public static function getInterp() { var interp = new Interp(); //interp.importEnabled = true; diff --git a/tests/src/tests/StringCase.hx b/tests/src/tests/StringCase.hx index f9c4d58..439706e 100644 --- a/tests/src/tests/StringCase.hx +++ b/tests/src/tests/StringCase.hx @@ -60,30 +60,6 @@ class StringCase extends TestCase { headerCode = ''; - Util.runKnownBug("string.code doesnt work", () -> { - assertEq('"f".code', "f".code); - assertEq('"o".code', "o".code); - assertEq('"1".code', "1".code); - assertEq('"b".code', "b".code); - assertEq('"a".code', "a".code); - assertEq('"r".code', "r".code); - assertEq('"foo".code', null); // multiple chars causes error - assertEq('"bar".code', null); // multiple chars causes error - }); - - Util.runKnownBug("String.fromCharCode doesnt work", () -> { - assertEq('String.fromCharCode(65)', String.fromCharCode(65)); - assertEq('String.fromCharCode(97)', String.fromCharCode(97)); - assertEq('String.fromCharCode(98)', String.fromCharCode(98)); - assertEq('String.fromCharCode(99)', String.fromCharCode(99)); - assertEq('String.fromCharCode(100)', String.fromCharCode(100)); - assertEq('String.fromCharCode(101)', String.fromCharCode(101)); - assertEq('String.fromCharCode(102)', String.fromCharCode(102)); - assertEq('String.fromCharCode(103)', String.fromCharCode(103)); - }); - - headerCode = ''; - assertEq('("3" > "11")', ("3" > "11")); assertEq('(" 3" < "3")', (" 3" < "3")); assertEq('("a" < "b")', ("a" < "b")); @@ -153,6 +129,41 @@ class StringCase extends TestCase { assertEq("'hello ${5} world'", 'hello ${5} world'); assertEq("'hello ${5}'", 'hello ${5}'); + + //Util.runKnownBug("string.code doesnt work", () -> { + assertEq('"f".code', "f".code); + assertEq('"o".code', "o".code); + assertEq('"1".code', "1".code); + assertEq('"b".code', "b".code); + assertEq('"a".code', "a".code); + assertEq('"r".code', "r".code); + assertEq('"r".code', "r".code); + assertError('"foo".code', Parser.getBaseError(EPreset(INVALID_CHAR_CODE_MULTI))); // multiple chars causes error + assertError('"bar".code', Parser.getBaseError(EPreset(INVALID_CHAR_CODE_MULTI))); // multiple chars causes error + //}); + + headerCode = ''; + var a = 103; + + //Util.runKnownBug("String.fromCharCode doesnt work", () -> { + assertEq('String.fromCharCode(65)', String.fromCharCode(65)); + assertEq('String.fromCharCode(97)', String.fromCharCode(97)); + assertEq('String.fromCharCode(98)', String.fromCharCode(98)); + assertEq('String.fromCharCode(99)', String.fromCharCode(99)); + assertEq('String.fromCharCode(100)', String.fromCharCode(100)); + assertEq('String.fromCharCode(101)', String.fromCharCode(101)); + assertEq('String.fromCharCode(102)', String.fromCharCode(102)); + assertEq('String.fromCharCode(103)', String.fromCharCode(103)); + assertEq('var a = 103; String.fromCharCode(a)', String.fromCharCode(test)); + //}); + + assertError("'hello ${}'", Parser.getBaseError(EPreset(EMPTY_INTERPOLATION))); + } + + static var test = 103; + + static function fromCharCode(a:Int) { + return String.fromCharCode(a); } override function teardown() { diff --git a/tests/src/tests/TestCase.hx b/tests/src/tests/TestCase.hx index 54eddb9..05fe3d5 100644 --- a/tests/src/tests/TestCase.hx +++ b/tests/src/tests/TestCase.hx @@ -1,6 +1,7 @@ package tests; import hscript.Printer; +import hscript.Tools; @:access(hscript.Interp) @:access(hscript.Parser) @@ -19,6 +20,32 @@ class TestCase extends HScriptRunner { return true; } + public function assertError(script:String, expectedError:hscript.Error, ?message:String, ?vars:Dynamic, ?pos:haxe.PosInfos) { + if(message == null) + message = script; + try { + var result = if(vars != null) + executeWithVarsUnsafe(script, vars); + else + executeUnsafe(script); + Sys.println("# For script: " + script); + Sys.println("## Expected error: " + Tools.cleanError(expectedError)); + Sys.println("## Got result: " + result); + Sys.println("> " + Printer.convertExprToString(lastExpr)); + return Util.failed(); + } catch(e:hscript.Error) { + if(Printer.compareErrors(e, expectedError)) { + return Util.passed(); + } else { + Sys.println("# For script: " + script); + Sys.println("## Expected error: " + Tools.cleanError(expectedError)); + Sys.println("## Actual error: " + Tools.cleanError(e)); + return Util.failed(); + } + } + return Util.failed(); + } + public function assertEqPrintable(script:String, expected:Dynamic, ?message:String, ?vars:Dynamic, ?pos:haxe.PosInfos) { if(message == null) message = script;