jsx
Version:
a faster, safer, easier JavaScript
1,621 lines (1,339 loc) • 68.6 kB
JSX
/*
* 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