From f464767ee729d1da016346c3dee0491a6e2ee1ed Mon Sep 17 00:00:00 2001 From: Nicolas Cannasse Date: Sun, 20 Oct 2019 11:16:23 +0200 Subject: [PATCH] added Parser.resumeError, added Checker.getCompletion --- hscript/Checker.hx | 62 +++++++++++++++++++++++++++++++++++++--------- hscript/Parser.hx | 47 ++++++++++++++++++++++++----------- 2 files changed, 83 insertions(+), 26 deletions(-) diff --git a/hscript/Checker.hx b/hscript/Checker.hx index f10142a..aa97184 100644 --- a/hscript/Checker.hx +++ b/hscript/Checker.hx @@ -53,6 +53,7 @@ typedef CClass = {> CNamedType, typedef CField = { var isPublic : Bool; + var complete : Bool; var params : Array; var name : String; var t : TType; @@ -69,6 +70,15 @@ typedef CTypedef = {> CNamedType, typedef CAbstract = {> CNamedType, } +class Completion { + public var expr : Expr; + public var t : TType; + public function new(expr,t) { + this.expr = expr; + this.t = t; + } +} + @:allow(hscript.Checker) class CheckerTypes { @@ -118,13 +128,17 @@ class CheckerTypes { for( f in c.fields ) { if( f.isOverride || f.name.substr(0,4) == "get_" || f.name.substr(0,4) == "set_" ) continue; var skip = false; - for( m in f.meta ) - if( m.name == ":noScript" ) { + var complete = !StringTools.startsWith(f.name,"__"); // __uid, etc. (no metadata in such fields) + for( m in f.meta ) { + if( m.name == ":noScript" ) { skip = true; break; } + if( m.name == ":noCompletion" ) + complete = false; + } if( skip ) continue; - var fl : CField = { isPublic : f.isPublic, params : [], name : f.name, t : null }; + var fl : CField = { isPublic : f.isPublic, complete : complete, params : [], name : f.name, t : null }; for( p in f.params ) { var pt = TParam(p); var key = f.name+"."+p; @@ -251,6 +265,7 @@ class Checker { var globals : Map = new Map(); var events : Map = new Map(); var currentFunType : TType; + var isCompletion : Bool; public var allowAsync : Bool; public var allowReturn : Null; @@ -283,9 +298,10 @@ class Checker { }]; } - public function check( expr : Expr, ?withType : WithType ) { + public function check( expr : Expr, ?withType : WithType, ?isCompletion = false ) { if( withType == null ) withType = NoValue; locals = new Map(); + this.isCompletion = isCompletion; switch( edef(expr) ) { case EBlock(el): var delayed = []; @@ -328,7 +344,7 @@ class Checker { inline function error( msg : String, curExpr : Expr ) { var e = ECustom(msg); #if hscriptPos var e = new Error(e, curExpr.pmin, curExpr.pmax, curExpr.origin, curExpr.line); #end - throw e; + if( !isCompletion ) throw e; } function saveLocals() { @@ -339,7 +355,10 @@ class Checker { return switch (t) { case CTPath(path, params): var ct = types.resolve(path.join("."),params == null ? [] : [for( p in params ) makeType(t,e)]); - if( ct == null ) error("Unknown type "+path, e); + if( ct == null ) { + error("Unknown type "+path, e); + ct = TDynamic; + } return ct; case CTFun(args, ret): var i = 0; @@ -647,7 +666,7 @@ class Checker { cf = c.fields.get("a_"+f); if( cf != null ) { var isPublic = true; // consider a_ prefixed as script specific - cf = { isPublic : isPublic, params : cf.params, name : cf.name, t : unasync(cf.t) }; + cf = { isPublic : isPublic, params : cf.params, name : cf.name, t : unasync(cf.t), complete : cf.complete }; if( cf.t == null ) cf = null; } } @@ -712,7 +731,16 @@ class Checker { return t.match(TInst({name:"String"},_)); } + function onCompletion( expr : Expr, t : TType ) { + if( isCompletion ) throw new Completion(expr, t); + } + function typeExpr( expr : Expr, withType : WithType ) : TType { + if( expr == null && isCompletion ) + return switch( withType ) { + case WithType(t): t; + default: TDynamic; + } switch( edef(expr) ) { case EConst(c): return switch (c) { @@ -738,6 +766,7 @@ class Checker { case "trace": return TDynamic; default: + if( isCompletion) return TDynamic; error("Unknown identifier "+v, expr); } case EBlock(el): @@ -764,24 +793,29 @@ class Checker { for( i in 0...params.length ) { var a = args[i]; if( a == null ) error("Too many arguments", params[i]); - var t = typeExpr(params[i], WithType(a.t)); + var t = typeExpr(params[i], a == null ? Value : WithType(a.t)); unify(t, a.t, params[i]); } for( i in params.length...args.length ) if( !args[i].opt ) - error("Missing argument '"+args[i].name+"'", expr); + error("Missing argument "+args[i].name+":"+typeStr(args[i].t), expr); return ret; case TDynamic: for( p in params ) typeExpr(p,Value); return makeMono(); default: error(typeStr(ft)+" cannot be called", e); + return makeMono(); } case EField(o, f): var ot = typeExpr(o, Value); + if( f == null ) + onCompletion(expr, ot); var ft = getField(ot, f, expr); - if( ft == null ) + if( ft == null ) { error(typeStr(ot)+" has no field "+f, expr); + ft = TDynamic; + } return ft; case ECheckType(v, t): var ct = makeType(t, expr); @@ -813,8 +847,11 @@ class Checker { var map = [for( f in tfields ) f.name => f]; return TAnon([for( f in fl ) { var ft = map.get(f.name); - if( ft == null ) error("Extra field "+f.name, f.e); - { t : typeExprWith(f.e, ft.t), opt : false, name : f.name } + var ft = if( ft == null ) { + error("Extra field "+f.name, f.e); + TDynamic; + } else ft.t; + { t : typeExprWith(f.e, ft), opt : false, name : f.name } }]); default: return TAnon([for( f in fl ) { t : typeExpr(f.e, Value), opt : false, name : f.name }]); @@ -1032,6 +1069,7 @@ class Checker { case ENew(cl, params): } error("Don't know how to type "+edef(expr).getName(), expr); + return TDynamic; } } \ No newline at end of file diff --git a/hscript/Parser.hx b/hscript/Parser.hx index 00e3ba1..67d9e5b 100644 --- a/hscript/Parser.hx +++ b/hscript/Parser.hx @@ -78,6 +78,11 @@ class Parser { **/ public var allowMetadata : Bool; + /** + resume from parsing errors (when parsing incomplete code, during completion for example) + **/ + public var resumeErrors : Bool; + // implementation var input : haxe.io.Input; var char : Int; @@ -142,6 +147,7 @@ class Parser { } public inline function error( err, pmin, pmax ) { + if( !resumeErrors ) #if hscriptPos throw new Error(err, pmin, pmax, origin, line); #else @@ -329,6 +335,7 @@ class Parser { break; default: unexpected(tk); + break; } ensure(TDoubleDot); fl.push({ name : id, e : parseExpr() }); @@ -422,7 +429,7 @@ class Parser { while( true ) { parseFullExpr(a); tk = token(); - if( tk == TBrClose ) + if( tk == TBrClose || (resumeErrors && tk == TEof) ) break; push(tk); } @@ -445,7 +452,7 @@ class Parser { case TBkOpen: var a = new Array(); tk = token(); - while( tk != TBkClose ) { + while( tk != TBkClose && (!resumeErrors || tk != TEof) ) { push(tk); a.push(parseExpr()); tk = token(); @@ -485,6 +492,7 @@ class Parser { break; default: unexpected(tk); + break; } } ensureToken(TOp("->")); @@ -537,6 +545,8 @@ class Parser { } function makeUnop( op, e ) { + if( e == null && resumeErrors ) + return null; return switch( expr(e) ) { case EBinop(bop, e1, e2): mk(EBinop(bop, makeUnop(op, e1), e2), pmin(e1), pmax(e2)); case ETernary(e1, e2, e3): mk(ETernary(makeUnop(op, e1), e2, e3), pmin(e1), pmax(e3)); @@ -545,6 +555,8 @@ class Parser { } function makeBinop( op, e1, e ) { + if( e == null && resumeErrors ) + return mk(EBinop(op,e1,e),pmin(e1),pmax(e1)); return switch( expr(e) ) { case EBinop(op2,e2,e3): if( opPriority.get(op) <= opPriority.get(op2) && !opRightAssoc.exists(op) ) @@ -644,16 +656,16 @@ class Parser { case "new": var a = new Array(); a.push(getIdent()); - var next = true; - while( next ) { + while( true ) { var tk = token(); switch( tk ) { case TDot: a.push(getIdent()); case TPOpen: - next = false; + break; default: unexpected(tk); + break; } } var args = parseExprList(TPClose); @@ -739,6 +751,7 @@ class Parser { break; default: unexpected(tk); + break; } } mk(ESwitch(e, cases, def), p1, tokenMax); @@ -809,7 +822,9 @@ class Parser { } switch( tk ) { case TId(id): name = id; - default: unexpected(tk); + default: + unexpected(tk); + break; } var arg : Argument = { name : name }; args.push(arg); @@ -830,8 +845,8 @@ class Parser { unexpected(tk); } } - } - return args; + } + return args; } function parseFunctionDecl() { @@ -891,6 +906,7 @@ class Parser { default: } unexpected(t); + break; } } else push(t); @@ -901,13 +917,13 @@ class Parser { case TPOpen: var a = token(), b = token(); - - push(b); + + push(b); push(a); function withReturn(args) { switch token() { // I think it wouldn't hurt if ensure used enumEq - case TOp('->'): + case TOp('->'): case t: unexpected(t); } @@ -916,7 +932,7 @@ class Parser { switch [a, b] { case [TPClose, _] | [TId(_), TDoubleDot]: - + var args = [for (arg in parseFunctionArgs()) { switch arg.value { case null: @@ -929,12 +945,12 @@ class Parser { return withReturn(args); default: - + var t = parseType(); return switch token() { case TComma: var args = [t]; - + while (true) { args.push(parseType()); if (!maybe(TComma)) break; @@ -973,6 +989,7 @@ class Parser { meta.push({ name : name, params : parseMetaArgs() }); default: unexpected(t); + break; } } return parseTypeNext(CTAnon(fields)); @@ -1017,6 +1034,7 @@ class Parser { default: if( tk == etk ) break; unexpected(tk); + break; } } return args; @@ -1219,6 +1237,7 @@ class Parser { }; default: unexpected(TId(id)); + break; } } return null;