Added String.fromCharCode, "a".code

Added a preprocessor
This commit is contained in:
NeeEoo
2024-04-25 17:23:35 +02:00
parent 6c77566fea
commit fb8f35e2a5
11 changed files with 277 additions and 111 deletions
+67
View File
@@ -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";
}
-38
View File
@@ -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<String> );
DImport( path : Array<String>, ?everything : Bool );
+6 -1
View File
@@ -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) {
+12
View File
@@ -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;
+58
View File
@@ -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
}
}
+8 -3
View File
@@ -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;
+8
View File
@@ -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);
+20
View File
@@ -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);
}
}
+36 -45
View File
@@ -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;
+35 -24
View File
@@ -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() {
+27
View File
@@ -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;