UNPKG

jsx

Version:

a faster, safer, easier JavaScript

1,621 lines (1,339 loc) 68.6 kB
/* * Copyright (c) 2012 DeNA Co., Ltd. * * 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. */ import "./analysis.jsx"; import "./classdef.jsx"; import "./parser.jsx"; import "./type.jsx"; import "./util.jsx"; import "./statement.jsx"; import "./optimizer.jsx"; abstract class Expression implements Stashable { var _token : Token; function constructor (token : Token) { this._token = token; } function constructor (that : Expression) { this._token = that.getToken(); for (var k in that._stash) this._stash[k] = that._stash[k].clone(); } abstract function clone () : Expression; abstract function serialize () : variant; function instantiate (instantiationContext : InstantiationContext) : boolean { function onExpr(expr : Expression) : boolean { if (expr instanceof NullExpression) { var srcType = expr.getType(); if (srcType != null) { (expr as NullExpression).setType(srcType.instantiate(instantiationContext)); } } else if (expr instanceof NewExpression) { var srcType = expr.getType(); if (srcType != null) { (expr as NewExpression).setType(srcType.instantiate(instantiationContext)); } } else if (expr instanceof ArrayLiteralExpression) { var srcType = expr.getType(); if (srcType != null) { (expr as ArrayLiteralExpression).setType(srcType.instantiate(instantiationContext)); } } else if (expr instanceof MapLiteralExpression) { var srcType = expr.getType(); if (srcType != null) { (expr as MapLiteralExpression).setType(srcType.instantiate(instantiationContext)); } } else if (expr instanceof AsExpression) { var srcType = expr.getType(); if (srcType != null) { (expr as AsExpression).setType(srcType.instantiate(instantiationContext)); } } else if (expr instanceof AsNoConvertExpression) { var srcType = expr.getType(); if (srcType != null) { (expr as AsNoConvertExpression).setType(srcType.instantiate(instantiationContext)); } } else if (expr instanceof ClassExpression) { var srcType = expr.getType(); if (srcType != null) { (expr as ClassExpression).setType(srcType.instantiate(instantiationContext)); } } else if (expr instanceof LocalExpression) { // update local to the instantiated one (expr as LocalExpression).setLocal((expr as LocalExpression).getLocal().getInstantiated()); } else if (expr instanceof InstanceofExpression) { var instanceofExpr = expr as InstanceofExpression; instanceofExpr.setExpectedType(instanceofExpr.getExpectedType().instantiate(instantiationContext)); } return expr.forEachExpression(onExpr); } return onExpr(this); } function getToken () : Token { return this._token; } abstract function analyze (context : AnalysisContext, parentExpr : Expression) : boolean; abstract function getType () : Type; function getHolderType () : Type { return null; } function forEachExpression (cb : function(:Expression):boolean) : boolean { return this.forEachExpression(function(expr : Expression, _ : function(:Expression):void){ return cb(expr); }); } abstract function forEachExpression (cb : function (:Expression, :function (:Expression) : void) : boolean) : boolean; function assertIsAssignable (context : AnalysisContext, token : Token, type : Type) : boolean { context.errors.push(new CompileError(token, "left-hand-side expression is not assignable")); return false; } static function assertIsAssignable (context : AnalysisContext, token : Token, lhsType : Type, rhsType : Type) : boolean { if (! lhsType.isAssignable()) { context.errors.push(new CompileError(token, "left-hand-side expression is not assignable")); return false; } if (! rhsType.isConvertibleTo(lhsType)) { context.errors.push(new CompileError(token, "cannot assign a value of type '" + rhsType.toString() + "' to '" + lhsType.toString() + "'")); return false; } return true; } static function getDefaultValueExpressionOf (type : Type) : Expression { if (type.equals(Type.booleanType)) return new BooleanLiteralExpression(new Token("false", false)); else if (type.equals(Type.integerType)) return new IntegerLiteralExpression(new Token("0", false)); else if (type.equals(Type.numberType)) return new NumberLiteralExpression(new Token("0", false)); else if (type.equals(Type.stringType)) return new StringLiteralExpression(new Token("\"\"", false)); else return new NullExpression(new Token("null", false), type); } } abstract class LeafExpression extends Expression { function constructor (token : Token) { super(token); } override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean { return true; } } abstract class OperatorExpression extends Expression { function constructor (token : Token) { super(token); } function constructor (that : Expression) { super(that); } function isConvertibleTo (context : AnalysisContext, expr : Expression, type : Type, mayUnbox : boolean) : boolean { var exprType = expr.getType().resolveIfNullable(); if (mayUnbox && type instanceof PrimitiveType && exprType instanceof ObjectType && exprType.getClassDef() == type.getClassDef()) return true; return exprType.isConvertibleTo(type); } function assertIsConvertibleTo (context : AnalysisContext, expr : Expression, type : Type, mayUnbox : boolean) : boolean { if (! this.isConvertibleTo(context, expr, type, mayUnbox)) { context.errors.push(new CompileError(this._token, "cannot apply operator '" + this._token.getValue() + "' to type '" + expr.getType().toString() + "'")); return false; } return true; } } // primary expressions class LocalExpression extends LeafExpression { var _local : LocalVariable; var _cloned : boolean; function constructor (token : Token, local : LocalVariable) { super(token); this._local = local; } override function clone () : LocalExpression { var that = new LocalExpression(this._token, this._local); that._cloned = true; return that; } function getLocal () : LocalVariable { return this._local; } function setLocal (local : LocalVariable) : void { this._local = local; } override function serialize () : variant { return [ "LocalExpression", this._token.serialize(), this._local.serialize() ] : variant[]; } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { // check that the variable is readable if ((parentExpr instanceof AssignmentExpression && (parentExpr as AssignmentExpression).getFirstExpr() == this && (parentExpr as AssignmentExpression).getToken().getValue() == "=") || (parentExpr == null && context.statement instanceof ForInStatement && (context.statement as ForInStatement).getLHSExpr() == this)) { // is LHS } else { this._local.touchVariable(context, this._token, false); if (this._local.getType() == null) return false; } return true; } override function getType () : Type { return this._local.getType(); } override function assertIsAssignable (context : AnalysisContext, token : Token, type : Type) : boolean { if (this._local.getType() == null) { if (type.equals(Type.nullType)) { context.errors.push(new CompileError(token, "cannot assign null without type annotation to a value of undetermined type")); return false; } this._local.setType(type.asAssignableType()); } else if (! type.isConvertibleTo(this._local.getType())) { context.errors.push(new CompileError(token, "cannot assign a value of type '" + type.toString() + "' to '" + this._local.getType().toString() + "'")); return false; } this._local.touchVariable(context, this._token, true); return true; } } class ClassExpression extends LeafExpression { var _parsedType : Type; function constructor (token : Token, parsedType : Type) { super(token); this._parsedType = parsedType; } override function clone () : ClassExpression { return new ClassExpression(this._token, this._parsedType); } override function serialize () : variant { return [ "ClassExpression", this._token.serialize(), this._parsedType.serialize() ] : variant[]; } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { return true; } override function getType () : Type { return this._parsedType; } function setType (type : Type) : void { this._parsedType = type; } override function assertIsAssignable (context : AnalysisContext, token : Token, type : Type) : boolean { context.errors.push(new CompileError(token, "cannot modify a class definition")); return false; } } class NullExpression extends LeafExpression { var _type : Type; function constructor (token : Token, type : Type) { super(token); this._type = type; } override function clone () : NullExpression { return new NullExpression(this._token, this._type); } override function serialize () : variant { return [ "NullExpression", this._token.serialize(), this._type.serialize() ] : variant[]; } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { return true; } override function getType () : Type { return this._type; } function setType (type : Type) : void { this._type = type; } } class BooleanLiteralExpression extends LeafExpression { function constructor (token : Token) { super(token); } override function clone () : BooleanLiteralExpression { return new BooleanLiteralExpression(this._token); } override function serialize () : variant { return [ "BooleanLiteralExpression", this._token.serialize() ] : variant[]; } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { return true; } override function getType () : Type { return Type.booleanType; } } class IntegerLiteralExpression extends LeafExpression { function constructor (token : Token) { super(token); } override function clone () : IntegerLiteralExpression { return new IntegerLiteralExpression(this._token); } override function serialize () : variant { return [ "IntegerLiteralExpression", this._token.serialize() ] : variant[]; } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { return true; } override function getType () : Type { return Type.integerType; } } class NumberLiteralExpression extends LeafExpression { function constructor (token : Token) { super(token); } override function clone () : NumberLiteralExpression { return new NumberLiteralExpression(this._token); } override function serialize () : variant { return [ "NumberLiteralExpression", this._token.serialize() ] : variant[]; } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { return true; } override function getType () : Type { return Type.numberType; } } class StringLiteralExpression extends LeafExpression { function constructor (token : Token) { super(token); } override function clone () : StringLiteralExpression { return new StringLiteralExpression(this._token); } override function serialize () : variant { return [ "StringLiteralExpression", this._token.serialize() ] : variant[]; } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { return true; } override function getType () : Type { return Type.stringType; } } class RegExpLiteralExpression extends LeafExpression { var _type : Type; function constructor (token : Token) { this(token, null); } function constructor (token : Token, type : Type) { super(token); this._type = type; } override function clone () : RegExpLiteralExpression { return new RegExpLiteralExpression(this._token, this._type); } override function serialize () : variant { return [ "RegExpLiteralExpression", this._token.serialize() ] : variant[]; } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { var classDef = context.parser.lookup(context.errors, this._token, "RegExp"); if (classDef == null) throw new Error("could not find definition for RegExp"); this._type = new ObjectType(classDef); return true; } override function getType () : Type { return this._type; } } class ArrayLiteralExpression extends Expression { var _exprs : Expression[]; var _type : Type; function constructor (token : Token, exprs : Expression[], type : Type) { super(token); this._exprs = exprs; this._type = type; // optional at this moment } override function clone () : ArrayLiteralExpression { return new ArrayLiteralExpression(this._token, Cloner.<Expression>.cloneArray(this._exprs), this._type); } function getExprs () : Expression[] { return this._exprs; } override function getType () : Type { return this._type; } function setType (type : Type) : void { this._type = type; } override function serialize () : variant { return [ "ArrayLiteralExpression", this._token.serialize(), Serializer.<Expression>.serializeArray(this._exprs), Serializer.<Type>.serializeNullable(this._type) ] : variant[]; } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { // analyze all elements var succeeded = true; for (var i = 0; i < this._exprs.length; ++i) { if (! this._exprs[i].analyze(context, this)) { succeeded = false; } else if (this._exprs[i].getType().equals(Type.voidType)) { // FIXME the location of the error would be strange; we deseparately need expr.getToken() context.errors.push(new CompileError(this._token, "cannot assign void to an array")); succeeded = false; } } if (! succeeded) return false; // determine the type from the array members if the type was not specified if (this._type != null) { var classDef; if (this._type instanceof ObjectType && (classDef = this._type.getClassDef()) instanceof InstantiatedClassDefinition && (classDef as InstantiatedClassDefinition).getTemplateClassName() == "Array") { //ok } else { context.errors.push(new CompileError(this._token, "the type specified after ':' is not an array type")); return false; } // check type of the elements var expectedType = (this._type.getClassDef() as InstantiatedClassDefinition).getTypeArguments()[0].toNullableType(); for (var i = 0; i < this._exprs.length; ++i) { var elementType = this._exprs[i].getType(); if (! elementType.isConvertibleTo(expectedType)) { context.errors.push(new CompileError(this._token, "cannot assign '" + elementType.toString() + "' to an array of '" + expectedType.toString() + "'")); succeeded = false; } } } else { var elementType = Type.calcLeastCommonAncestor(this._exprs.map.<Type>((expr) -> { return expr.getType(); }), true); if (elementType == null || elementType.equals(Type.nullType)) { context.errors.push(new CompileError(this._token, "could not deduce array type, please specify")); return false; } if (elementType.equals(Type.integerType)) elementType = Type.numberType; elementType = elementType.resolveIfNullable(); this._type = new ObjectType(Util.instantiateTemplate(context, this._token, "Array", [ elementType ])); } return succeeded; } override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean { if (! Util.forEachExpression(cb, this._exprs)) return false; return true; } } class MapLiteralElement { var _key : Token; var _expr : Expression; function constructor (key : Token, expr : Expression) { this._key = key; this._expr = expr; } function getKey () : Token { return this._key; } function getExpr () : Expression { return this._expr; } function setExpr (expr : Expression) : void { this._expr = expr; } function serialize () : variant { return [ this._key.serialize(), this._expr.serialize() ] : variant[]; } } class MapLiteralExpression extends Expression { var _elements : MapLiteralElement[]; var _type : Type; function constructor (token : Token, elements : MapLiteralElement[], type : Type) { super(token); this._elements = elements; this._type = type; // optional at this moment } override function clone () : MapLiteralExpression { var ret = new MapLiteralExpression(this._token, new MapLiteralElement[], this._type); for (var i = 0; i < this._elements.length; ++i) ret._elements[i] = new MapLiteralElement(this._elements[i].getKey(), this._elements[i].getExpr().clone()); return ret; } function getElements () : MapLiteralElement[] { return this._elements; } override function getType () : Type { return this._type; } function setType (type : Type) : void { this._type = type; } override function serialize () : variant { return [ "MapLiteralExpression", this._token.serialize(), Serializer.<MapLiteralElement>.serializeArray(this._elements), Serializer.<Type>.serializeNullable(this._type) ] : variant[]; } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { // analyze all elements var succeeded = true; for (var i = 0; i < this._elements.length; ++i) { if (! this._elements[i].getExpr().analyze(context, this)) { succeeded = false; } else if (this._elements[i].getExpr().getType().equals(Type.voidType)) { // FIXME the location of the error would be strange; we deseparately need expr.getToken() context.errors.push(new CompileError(this._token, "cannot assign void to a hash")); succeeded = false; } } if (! succeeded) return false; // determine the type from the array members if the type was not specified if (this._type != null && this._type == Type.variantType) { // pass } else if (this._type != null && this._type instanceof ObjectType) { var classDef = this._type.getClassDef(); if (! (classDef instanceof InstantiatedClassDefinition && (classDef as InstantiatedClassDefinition).getTemplateClassName() == "Map")) { context.errors.push(new CompileError(this._token, "specified type is not a hash type")); return false; } var expectedType = (this._type as ParsedObjectType).getTypeArguments()[0]; // check type of the elements (expect when expectedType == null, meaning that it is a variant) for (var i = 0; i < this._elements.length; ++i) { var elementType = this._elements[i].getExpr().getType(); if (! elementType.isConvertibleTo(expectedType)) { context.errors.push(new CompileError(this._token, "cannot assign '" + elementType.toString() + "' to a map of '" + expectedType.toString() + "'")); succeeded = false; } } } else if (this._type != null) { context.errors.push(new CompileError(this._token, "invalid type for a map literal")); return false; } else { var elementType = Type.calcLeastCommonAncestor(this._elements.map.<Type>((elt) -> { return elt.getExpr().getType(); }), true); if (elementType == null || elementType.equals(Type.nullType)) { context.errors.push(new CompileError(this._token, "could not deduce hash type, please specify")); return false; } if (elementType.equals(Type.integerType)) elementType = Type.numberType; elementType = elementType.resolveIfNullable(); this._type = new ObjectType(Util.instantiateTemplate(context, this._token, "Map", [ elementType ])); } return succeeded; } override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean { for (var i = 0; i < this._elements.length; ++i) { if (! cb(this._elements[i].getExpr(), function (elements : MapLiteralElement[], index : number) : function(:Expression):void { return function (expr) { elements[index].setExpr(expr); }; }(this._elements, i))) { return false; } } return true; } } class ThisExpression extends Expression { var _classDef : ClassDefinition; function constructor (token : Token, classDef : ClassDefinition) { super(token); this._classDef = classDef; } override function clone () : ThisExpression { return new ThisExpression(this._token, this._classDef); } override function serialize () : variant { return [ "ThisExpression", this._token.serialize(), Serializer.<ClassDefinition>.serializeNullable(this._classDef) ] : variant[]; } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { var rootFuncDef = context.funcDef; if (rootFuncDef != null) while (rootFuncDef.getParent() != null) rootFuncDef = rootFuncDef.getParent(); if (rootFuncDef == null || (rootFuncDef.flags() & ClassDefinition.IS_STATIC) != 0) { context.errors.push(new CompileError(this._token, "cannot use 'this' outside of a member function")); return false; } this._classDef = rootFuncDef.getClassDef(); return true; } override function getType () : Type { return new ObjectType(this._classDef); } override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean { return true; } } class FunctionExpression extends Expression { var _funcDef : MemberFunctionDefinition; function constructor (token : Token, funcDef : MemberFunctionDefinition) { super(token); this._funcDef = funcDef; } override function clone () : FunctionExpression { // NOTE: funcDef is not cloned, but is later replaced in MemberFunctionDefitition#instantiate return new FunctionExpression(this._token, this._funcDef); } function getFuncDef () : MemberFunctionDefinition { return this._funcDef; } function setFuncDef (funcDef : MemberFunctionDefinition) : void { this._funcDef = funcDef; } override function serialize () : variant { return [ "FunctionExpression", this._funcDef.serialize() ] : variant[]; } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { if (! this.argumentTypesAreIdentified()) { context.errors.push(new CompileError(this._token, "argument types were not automatically deductable, please specify them by hand")); return false; } this._funcDef.analyze(context); return true; // return true since everything is resolved correctly even if analysis of the function definition failed } override function getType () : Type { return this._funcDef.getType(); } function argumentTypesAreIdentified () : boolean { var argTypes = this._funcDef.getArgumentTypes(); for (var i = 0; i < argTypes.length; ++i) { if (argTypes[i] == null) return false; } return true; } function typesAreIdentified () : boolean { if (! this.argumentTypesAreIdentified()) return false; if (this._funcDef.getReturnType() == null) return false; return true; } function deductTypeIfUnknown (context : AnalysisContext, type : ResolvedFunctionType) : boolean { if (! this._funcDef.deductTypeIfUnknown(context, type)) return false; return true; } override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean { return true; } } // unary expressions abstract class UnaryExpression extends OperatorExpression { var _expr : Expression; function constructor (operatorToken : Token, expr : Expression) { super(operatorToken); this._expr = expr; } function getExpr () : Expression { return this._expr; } function setExpr (expr : Expression) : void { this._expr = expr; } override function serialize () : variant { return [ "UnaryExpression", this._token.serialize(), this._expr.serialize() ] : variant[]; } function _analyze (context : AnalysisContext) : boolean { if (! this._expr.analyze(context, this)) return false; if (this._expr.getType().equals(Type.voidType)) { context.errors.push(new CompileError(this._token, "cannot apply operator '" + this._token.getValue() + "' against void")); return false; } return true; } override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean { return cb(this._expr, function (expr) { this._expr = expr; }); } } class BitwiseNotExpression extends UnaryExpression { function constructor (operatorToken : Token, expr : Expression) { super(operatorToken, expr); } override function clone () : BitwiseNotExpression { return new BitwiseNotExpression(this._token, this._expr.clone()); } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { if (! this._analyze(context)) return false; if (! this.assertIsConvertibleTo(context, this._expr, Type.numberType, false)) return false; return true; } override function getType () : Type { return Type.integerType; } } class InstanceofExpression extends UnaryExpression { var _expectedType : Type; function constructor (operatorToken : Token, expr : Expression, expectedType : Type) { super(operatorToken, expr); this._expectedType = expectedType; } override function clone () : InstanceofExpression { return new InstanceofExpression(this._token, this._expr.clone(), this._expectedType); } function getExpectedType () : Type { return this._expectedType; } function setExpectedType (type : Type) : void { this._expectedType = type; } override function serialize () : variant { return [ "InstanceofExpression", this._expr.serialize(), this._expectedType.serialize() ] : variant[]; } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { if (! this._analyze(context)) return false; var exprType = this._expr.getType(); if (exprType instanceof ObjectType) { } else if (exprType.equals(Type.variantType)) { } else { context.errors.push(new CompileError(this._token, "operator 'instanceof' is only applicable to an object or a variant")); return false; } return true; } override function getType () : Type { return Type.booleanType; } } class AsExpression extends UnaryExpression { var _type : Type; function constructor (operatorToken : Token, expr : Expression, type : Type) { super(operatorToken, expr); this._type = type; } override function clone () : AsExpression { return new AsExpression(this._token, this._expr.clone(), this._type); } override function serialize () : variant { return [ "AsExpression", this._expr.serialize(), this._type.serialize() ] : variant[]; } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { if (! this._analyze(context)) return false; if (this._type instanceof NullableType) { context.errors.push(new CompileError(this._token, "right operand of 'as' expression cannot be a Nullable<T> type")); return false; } // nothing to care if the conversion is allowed by implicit conversion if (this._expr.getType().isConvertibleTo(this._type)) return true; // possibly unsafe conversions var exprType = this._expr.getType().resolveIfNullable(); var success = false; if (exprType.equals(Type.nullType)) { if (this._type instanceof ObjectType || this._type instanceof FunctionType) { // ok success = true; } } else if (exprType instanceof PrimitiveType) { if (this._type instanceof PrimitiveType) { // ok: primitive => primitive success = true; } } else if (exprType.equals(Type.variantType)) { // ok, variant is convertible to all types of objects success = true; } else if (exprType instanceof ObjectType) { // FIXME? conversion from objects to primitives should be done by calling toString(), valueOf(), etc. (optimized by emitter) if (this._type instanceof ObjectType && this._type.isConvertibleTo(exprType)) { // is down-cast, maybe unsafe success = true; } } else if (this._expr instanceof PropertyExpression && exprType instanceof FunctionType && this._type instanceof StaticFunctionType) { var deducedType = (this._expr as PropertyExpression).deduceByArgumentTypes(context, this._token, (this._type as StaticFunctionType).getArgumentTypes(), true); if (deducedType != null) { exprType = deducedType; if (deducedType.getReturnType().equals((this._type as StaticFunctionType).getReturnType())) { success = true; } } } if (! success) { context.errors.push(new CompileError(this._token, "cannot convert a value of type '" + exprType.toString() + "' to '" + this._type.toString() + "'")); return false; } return true; } override function getType () : Type { return this._type; } function setType (type : Type) : void { this._type = type; } } class AsNoConvertExpression extends UnaryExpression { var _type : Type; function constructor (operatorToken : Token, expr : Expression, type : Type) { super(operatorToken, expr); this._type = type; } override function clone () : AsNoConvertExpression { return new AsNoConvertExpression(this._token, this._expr.clone(), this._type); } override function serialize () : variant { return [ "AsNoConvertExpression", this._expr.serialize(), this._type.serialize() ] : variant[]; } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { if (! this._analyze(context)) return false; var srcType = this._expr.getType(); if ((srcType.equals(Type.nullType) && ! (this._type instanceof NullableType || this._type instanceof ObjectType || this._type instanceof FunctionType))) { context.errors.push(new CompileError(this._token, "'" + srcType.toString() + "' cannot be treated as a value of type '" + this._type.toString() + "'")); return false; } return true; } override function getType () : Type { return this._type; } function setType (type : Type) : void { this._type = type; } } class LogicalNotExpression extends UnaryExpression { function constructor (operatorToken : Token, expr : Expression) { super(operatorToken, expr); } override function clone () : LogicalNotExpression { return new LogicalNotExpression(this._token, this._expr.clone()); } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { if (! this._analyze(context)) return false; if (this._expr.getType().resolveIfNullable().equals(Type.voidType)) { context.errors.push(new CompileError(this._token, "cannot apply operator '!' against void")); return false; } return true; } override function getType () : Type { return Type.booleanType; } } abstract class IncrementExpression extends UnaryExpression { function constructor (operatorToken : Token, expr : Expression) { super(operatorToken, expr); } override function serialize () : variant { return [ this._getClassName(), this._token.serialize(), this._expr.serialize() ] : variant[]; } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { if (! this._analyze(context)) return false; var exprType = this._expr.getType(); if (exprType.resolveIfNullable().equals(Type.integerType) || exprType.resolveIfNullable().equals(Type.numberType)) { // ok } else { context.errors.push(new CompileError(this._token, "cannot apply operator '" + this._token.getValue() + "' to a non-number")); return false; } if (! this._expr.assertIsAssignable(context, this._token, exprType)) return false; return true; } override function getType () : Type { return this._expr.getType().resolveIfNullable(); } abstract function _getClassName () : string; } class PostIncrementExpression extends IncrementExpression { function constructor (operatorToken : Token, expr : Expression) { super(operatorToken, expr); } override function clone () : PostIncrementExpression { return new PostIncrementExpression(this._token, this._expr.clone()); } override function _getClassName () : string { return "PostIncrementExpression"; } } class PreIncrementExpression extends IncrementExpression { function constructor (operatorToken : Token, expr : Expression) { super(operatorToken, expr); } override function clone () : PreIncrementExpression { return new PreIncrementExpression(this._token, this._expr.clone()); } override function _getClassName () : string { return "PreIncrementExpression"; } } class PropertyExpression extends UnaryExpression { var _identifierToken : Token; var _typeArgs : Type[]; var _type : Type; function constructor (operatorToken : Token, expr1 : Expression, identifierToken : Token, typeArgs : Type[]) { this(operatorToken, expr1, identifierToken, typeArgs, null); } function constructor (operatorToken : Token, expr1 : Expression, identifierToken : Token, typeArgs : Type[], type : Type) { super(operatorToken, expr1); this._identifierToken = identifierToken; this._typeArgs = typeArgs; this._type = type != null ? type : null; } override function clone () : PropertyExpression { return new PropertyExpression(this._token, this._expr.clone(), this._identifierToken, this._typeArgs, this._type); } function getIdentifierToken () : Token { return this._identifierToken; } function getTypeArguments () : Type[] { return this._typeArgs; } override function serialize () : variant { return [ "PropertyExpression", this._expr.serialize(), this._identifierToken.serialize(), Serializer.<Type>.serializeNullable(this._type) ] : variant[]; } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { // normal handling if (! this._analyze(context)) return false; var exprType = this._expr.getType(); if (exprType.equals(Type.voidType)) { context.errors.push(new CompileError(this._identifierToken, "cannot obtain a member of void")); return false; } if (exprType.equals(Type.nullType)) { context.errors.push(new CompileError(this._identifierToken, "cannot obtain a member of null")); return false; } if (exprType.resolveIfNullable().equals(Type.variantType)) { context.errors.push(new CompileError(this._identifierToken, "property of a variant should be referred to by using the [] operator")); return false; } var classDef = exprType.getClassDef(); if (classDef == null) { context.errors.push(new CompileError(this._identifierToken, "cannot determine type due to preceding errors")); return false; } this._type = classDef.getMemberTypeByName( context.errors, this._identifierToken, this._identifierToken.getValue(), this._expr instanceof ClassExpression, this._typeArgs, (this._expr instanceof ClassExpression) ? ClassDefinition.GET_MEMBER_MODE_CLASS_ONLY : ClassDefinition.GET_MEMBER_MODE_ALL); if (this._type == null) { context.errors.push(new CompileError(this._identifierToken, "'" + exprType.toString() + "' does not have a property named '" + this._identifierToken.getValue() + "'")); return false; } return true; } override function getType () : Type { return this._type; } override function getHolderType () : Type { var type = this._expr.getType(); if (type instanceof PrimitiveType) type = new ObjectType(type.getClassDef()); return type; } override function assertIsAssignable (context : AnalysisContext, token : Token, type : Type) : boolean { if (! Expression.assertIsAssignable(context, token, this._type, type)) return false; // check constness (note: a possibly assignable property is always a member variable) var holderType = this.getHolderType(); var varFlags = 0; if (! holderType.equals(Type.variantType)) { if (holderType.getClassDef().forEachClassToBase(function (classDef) { return classDef.forEachMemberVariable(function (varDef) { if (varDef.name() == this._identifierToken.getValue()) { // found varFlags = varDef.flags(); return false; } return true; }); })) { throw new Error("logic flaw, could not find definition for " + holderType.getClassDef().className() + "#" + this._identifierToken.getValue()); } } if ((varFlags & ClassDefinition.IS_CONST) != 0) { context.errors.push(new CompileError(token, "cannot modify a constant")); return false; } else if ((varFlags & ClassDefinition.IS_READONLY) != 0) { context.errors.push(new CompileError(token, "cannot modify a readonly variable")); return false; } return true; } function deduceByArgumentTypes (context : AnalysisContext, operatorToken : Token, argTypes : Type[], isStatic : boolean) : ResolvedFunctionType { for (var i = 0; i < argTypes.length; ++i) { if (argTypes[i] instanceof FunctionChoiceType) { context.errors.push(new CompileError(operatorToken, "type deduction of overloaded function passed in as an argument is not supported; use 'as' to specify the function")); return null; } } var rhsType = (this._type as FunctionType).deduceByArgumentTypes(context, operatorToken, argTypes, isStatic); if (rhsType == null) return null; this._type = rhsType; return rhsType; } } class TypeofExpression extends UnaryExpression { function constructor (operatorToken : Token, expr : Expression) { super(operatorToken, expr); } override function clone () : TypeofExpression { return new TypeofExpression(this._token, this._expr.clone()); } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { if (! this._analyze(context)) return false; var exprType = this._expr.getType(); if (! exprType.equals(Type.variantType)) { context.errors.push(new CompileError(this._token, "cannot apply operator 'typeof' to '" + this._expr.getType().toString() + "'")); return false; } return true; } override function getType () : Type { return Type.stringType; } } class SignExpression extends UnaryExpression { function constructor (operatorToken : Token, expr : Expression) { super(operatorToken, expr); } override function clone () : SignExpression { return new SignExpression(this._token, this._expr.clone()); } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { if (! this._analyze(context)) return false; if (! this.assertIsConvertibleTo(context, this._expr, Type.numberType, true)) return false; return true; } override function getType () : Type { var type = this._expr.getType(); if (type.resolveIfNullable().equals(Type.numberType)) return Type.numberType; else return Type.integerType; } } // binary expressions abstract class BinaryExpression extends OperatorExpression { var _expr1 : Expression; var _expr2 : Expression; function constructor (operatorToken : Token, expr1 : Expression, expr2 : Expression) { super(operatorToken); this._expr1 = expr1; this._expr2 = expr2; } function getFirstExpr () : Expression { return this._expr1; } function setFirstExpr (expr : Expression) : void { this._expr1 = expr; } function getSecondExpr () : Expression { return this._expr2; } function setSecondExpr (expr : Expression) : void { this._expr2 = expr; } override function serialize () : variant { return [ "BinaryExpression", this._token.serialize(), this._expr1.serialize(), this._expr2.serialize()/*, Util.serializeNullable(this.getType())*/ ] : variant[]; } function _analyze (context : AnalysisContext) : boolean { if (! this._expr1.analyze(context, this)) return false; if (! this._expr2.analyze(context, this)) return false; return true; } override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean { if (! cb(this._expr1, function (expr) { this._expr1 = expr; })) return false; if (! cb(this._expr2, function (expr) { this._expr2 = expr; })) return false; return true; } } class AdditiveExpression extends BinaryExpression { var _type : Type; function constructor (operatorToken : Token, expr1 : Expression, expr2 : Expression) { super(operatorToken, expr1, expr2); this._type = null; } override function clone () : AdditiveExpression { var ret = new AdditiveExpression(this._token, this._expr1.clone(), this._expr2.clone()); ret._type = this._type; return ret; } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { if (! this._analyze(context)) return false; var expr1Type = this._expr1.getType().resolveIfNullable(); var expr2Type = this._expr2.getType().resolveIfNullable(); if ((expr1Type.isConvertibleTo(Type.numberType) || (expr1Type instanceof ObjectType && expr1Type.getClassDef() == Type.numberType.getClassDef())) && (expr2Type.isConvertibleTo(Type.numberType) || (expr2Type instanceof ObjectType && expr2Type.getClassDef() == Type.numberType.getClassDef()))) { // ok this._type = (expr1Type instanceof NumberType) || (expr2Type instanceof NumberType) ? (Type.numberType as Type) : (Type.integerType as Type); } else if ((expr1Type.equals(Type.stringType) || (expr1Type instanceof ObjectType && expr1Type.getClassDef() == Type.stringType.getClassDef())) && (expr2Type.equals(Type.stringType) || (expr2Type instanceof ObjectType && expr2Type.getClassDef() == Type.stringType.getClassDef()))) { // ok this._type = expr1Type; } else { context.errors.push(new CompileError(this._token, "cannot apply operator '+' to '" + expr1Type.toString() + "' and '" + expr2Type.toString() + "'")); return false; } return true; } override function getType () : Type { return this._type; } } class ArrayExpression extends BinaryExpression { var _type : Type; function constructor (operatorToken : Token, expr1 : Expression, expr2 : Expression) { super(operatorToken, expr1, expr2); this._type = null; } override function clone () : ArrayExpression { var ret = new ArrayExpression(this._token, this._expr1.clone(), this._expr2.clone()); ret._type = this._type; return ret; } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { if (! this._analyze(context)) return false; if (this._expr1.getType() == null) { context.errors.push(new CompileError(this._token, "cannot determine type due to preceding errors")); return false; } // obtain classDef var expr1Type = this._expr1.getType().resolveIfNullable(); if (expr1Type instanceof ObjectType) { return this._analyzeApplicationOnObject(context, expr1Type); } else if (expr1Type.equals(Type.variantType)) { return this._analyzeApplicationOnVariant(context); } context.errors.push(new CompileError(this._token, "cannot apply []; the operator is only applicable against an array or an variant")); return false; } function _analyzeApplicationOnObject (context : AnalysisContext, expr1Type : Type) : boolean { var expr1ClassDef = expr1Type.getClassDef(); assert expr1ClassDef; // obtain type of operator [] var funcType = expr1ClassDef.getMemberTypeByName(context.errors, this._token, "__native_index_operator__", false, new Type[], ClassDefinition.GET_MEMBER_MODE_ALL) as FunctionType; if (funcType == null) { context.errors.push(new CompileError(this._token, "cannot apply operator[] on an instance of class '" + expr1ClassDef.className() + "'")); return false; } // check type of expr2 var deducedFuncType = funcType.deduceByArgumentTypes(context, this._token, [ this._expr2.getType() ], false); if (deducedFuncType == null) { return false; } // set type of the expression this._type = deducedFuncType.getReturnType(); return true; } function _analyzeApplicationOnVariant (context : AnalysisContext) : boolean { var expr2Type = this._expr2.getType().resolveIfNullable(); if (! (expr2Type.equals(Type.stringType) || expr2Type.isConvertibleTo(Type.numberType))) { context.errors.push(new CompileError(this._token, "the argument of variant[] should be a string or a number")); return false; } this._type = Type.variantType; return true; } override function getType () : Type { return this._type; } override function assertIsAssignable (context : AnalysisContext, token : Token, type : Type) : boolean { return Expression.assertIsAssignable(context, token, this._type, type); } } class AssignmentExpression extends BinaryExpression { function constructor (operatorToken : Token, expr1 : Expression, expr2 : Expression) { super(operatorToken, expr1, expr2); } override function clone () : AssignmentExpression { return new AssignmentExpression(this._token, this._expr1.clone(), this._expr2.clone()); } override function analyze (context : AnalysisContext, parentExpr : Expression) : boolean { // special handling for v = function () ... if (this._expr2 instanceof FunctionExpression) return this._analyzeFunctionExpressionAssignment(context, parentExpr); // normal handling if (! this._analyze(context)) return false; if (this._token.getValue() != "=") return this._analyzeFusedAssignment(context); var rhsType = this._expr2.getType(); if (rhsType == null) return false; if (rhsType.equals(Type.voidType)) { context.errors.push(new CompileError(this._token, "cannot assign void")); return false; } if (this._expr2 instanceof ClassExpression) { context.errors.push(new CompileError(this._token, "cannot assign a class")); return false; } if (rhsType.resolveIfNullable().equals(Type.nullType) && this._expr1.getType() == null) { context.errors.push(new CompileError(this._token, "cannot assign null to an unknown type")); return false; } if (rhsType instanceof FunctionChoiceType) { var lhsType = this._expr1.getType(); if (lhsType != null) { if (! (lhsType instanceof ResolvedFunctionType)) { context.errors.push(new CompileError(this._token, "cannot assign a function reference to '" + this._expr1.getType().toString() + "'")); return false; } if ((rhsType = (this._expr2 as PropertyExpression).deduceByArgumentTypes(context, this._token, (lhsType as ResolvedFunctionType).getArgumentTypes(), lhsType instanceof StaticFunctionType)) == null) return false; } else { context.errors.push(new CompileError(this._token, "function reference is ambiguous")); return false; } } // assert that rhs type is not a member function, after resolving the function if (rhsType instanceof MemberFunctionType) { context.errors.push(new CompileError(this._token, "cannot assign a member function")); return false; } if (! this._expr1.assertIsAssignable(context, this._token, rhsType)) return false; return true; } function _analyzeFusedAssignment (context : AnalysisContext) : boolean { var lhsType = this._expr1.getType().resolveIfNullable(); var rhsType = this._expr2.getType().resolveIfNullable(); if (! this._expr1.assertIsAssignable(context, this._token, lhsType)) { return false; } if (this._token.getValue() == "+=" && lhsType.equals(Type.stringType) && rhsType.equals(Type.stringType)) return true; if (Type.isIntegerOrNumber(lhsType) && Type.isIntegerOrNumber(rhsType)) return true; context.errors.push(new CompileError(this._token, "cannot apply operator '" + this._token.getValue() + "' against '" + this._expr1.getType().toString() + "' and '" + this._expr2.getType().toString() + "'")); return false; } function _analyzeFunctionExpressionAssignment (context : AnalysisContext, parentExpr : Expression) : boolean { // analyze from left to right to avoid "variable may not be defined" error in case the function calls itself if (! this._expr1.analyze(context, this)) return false; if (this._expr1.getType() == null) { if (! (this._expr2 as FunctionExpression).typesAreIdentified()) { context.errors.push(new CompileError(this._token, "either side of the operator should be fully type-qualified : " + ((this._expr2 as FunctionExpression).argumentTypesAreIdentified() ? "return type not declared" : "argument / return types not declared"))); return false; } } else if (! this._expr1.getType().equals(Type.variantType)) { if (! (this._expr2 as FunctionExpression).deductTypeIfUnknown(context, this._expr1.getType() as ResolvedFunctionType)) { return false; } } if (! this._expr1.assertIsAssignable(context, this._token, this._expr2.getType())) return false; if (! this._expr2.analyze(context, this)) return false; return true; } override function getType () : Type { return this._expr1.getType(); } } // + - * / % < <= > >= & | ^ class BinaryNumberExpression extends BinaryExpression { function constructor (operatorToken : Token, expr1 : Expression, expr2 : Expression) { super(operatorToken, expr1, expr2); } override function clone () : BinaryNumberExpression { return new BinaryNumberExpression(this._token, this._expr1.clone(), this._expr2.clone()); } override function analyze (context : Ana