jsx
Version:
a faster, safer, easier JavaScript
1,641 lines (1,359 loc) • 52.5 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 "./expression.jsx";
import "./type.jsx";
import "./util.jsx";
import "./parser.jsx";
import "./optimizer.jsx";
abstract class Statement implements Stashable {
function constructor () {
}
// returns whether or not to continue analysing the following statements
function analyze (context : AnalysisContext) : boolean {
if (! (this instanceof CaseStatement || this instanceof DefaultStatement))
if (! Statement.assertIsReachable(context, this.getToken()))
return false;
try {
return this.doAnalyze(context);
} catch (e : Error) {
var token = this.getToken();
var srcPos = token != null ? Util.format(" at file %1, line %2", [token.getFilename(), token.getLineNumber() as string]) : "";
e.message = Util.format("fatal error while compiling statement%1\n%2", [srcPos, e.message]);
throw e;
}
}
function forEachStatement (cb : function(:Statement):boolean) : boolean {
return true;
}
function handleStatements (cb : function(:Statement[]):boolean) : boolean {
return true;
}
function forEachExpression (cb : function (:Expression):boolean) : boolean {
return this.forEachExpression(function (expr : Expression, _ : function(:Expression):void) : boolean {
return cb(expr);
});
}
abstract function forEachExpression (cb : function (:Expression, :function(:Expression):void) : boolean) : boolean;
abstract function getToken () : Token; // returns a token of the statement
abstract function doAnalyze(context : AnalysisContext) : boolean; // returns whether or not to continue analysing the following statements
function _analyzeExpr (context : AnalysisContext, expr : Expression) : boolean {
if (context.statement != null)
throw new Error("logic flaw");
context.statement = this;
var result = false;
try {
result = expr.analyze(context, null);
} finally {
context.statement = null;
}
return result;
}
static function assertIsReachable (context : AnalysisContext, token : Token) : boolean {
if (! context.getTopBlock().localVariableStatuses.isReachable()) {
context.errors.push(new CompileWarning(token, "the code is unreachable"));
}
return true;
}
abstract function clone () : Statement;
abstract function serialize () : variant;
}
class ConstructorInvocationStatement extends Statement {
var _token : Token;
var _ctorClassType : Type;
var _args : Expression[];
var _ctorFunctionType : FunctionType;
function constructor (token : Token, ctorClassType : Type, args : Expression[]) {
this(token, ctorClassType, args, null);
}
function constructor (token : Token, ctorClassType : Type, args : Expression[], ctorFunctionType : FunctionType) {
super();
this._token = token;
this._ctorClassType = ctorClassType;
this._args = args;
this._ctorFunctionType = ctorFunctionType != null ? ctorFunctionType : null;
}
override function clone () : Statement {
return new ConstructorInvocationStatement(this._token, this._ctorClassType, Cloner.<Expression>.cloneArray(this._args), this._ctorFunctionType);
}
function instantiate (instantiationContext : InstantiationContext) : Statement {
if (this._ctorFunctionType != null) {
throw new Error("instantiation after analysis?");
}
return new ConstructorInvocationStatement(
this._token,
this._ctorClassType.instantiate(instantiationContext),
Cloner.<Expression>.cloneArray(this._args),
null);
}
override function getToken () : Token {
return this._token;
}
function getArguments () : Expression[] {
return this._args;
}
function getConstructingClassDef () : ClassDefinition {
return this._ctorClassType.getClassDef();
}
function getConstructorType () : FunctionType {
return this._ctorFunctionType;
}
override function serialize () : variant {
return [
"ConstructorInvocationStatement",
this._ctorClassType.serialize(),
Serializer.<Expression>.serializeArray(this._args)
] : variant[];
}
override function doAnalyze (context : AnalysisContext) : boolean {
var ctorType = this.getConstructingClassDef().getMemberTypeByName(context.errors, this._token, "constructor", false, new Type[], ClassDefinition.GET_MEMBER_MODE_CLASS_ONLY) as FunctionType;
if (ctorType == null) {
if (this._args.length != 0) {
context.errors.push(new CompileError(this.getToken(), "no function with matching arguments"));
return true;
}
ctorType = new MemberFunctionType(this.getConstructingClassDef().getToken(), new ObjectType(this.getConstructingClassDef()), Type.voidType, new Type[], false); // implicit constructor
} else {
// analyze args
var argTypes = Util.analyzeArgs(
context, this._args, null,
ctorType.getExpectedTypes(this._args.length, false));
if (argTypes == null) {
// error is reported by callee
return true;
}
if ((ctorType = ctorType.deduceByArgumentTypes(context, this.getToken(), argTypes, false)) == null) {
// error is reported by callee
return true;
}
}
this._ctorFunctionType = ctorType;
return true;
}
override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean {
if (! Util.forEachExpression(cb, this._args))
return false;
return true;
}
}
// statements that take one expression
abstract class UnaryExpressionStatement extends Statement {
var _expr : Expression;
function constructor (expr : Expression) {
super();
if (expr == null)
throw new Error("logic flaw");
this._expr = expr;
}
override function getToken () : Token {
return this._expr.getToken();
}
function getExpr () : Expression {
return this._expr;
}
function setExpr (expr : Expression) : void {
this._expr = expr;
}
override function doAnalyze (context : AnalysisContext) : boolean {
this._analyzeExpr(context, this._expr);
return true;
}
override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean {
if (! cb(this._expr, function (expr) { this._expr = expr; }))
return false;
return true;
}
}
class ExpressionStatement extends UnaryExpressionStatement {
function constructor (expr : Expression) {
super(expr);
}
override function clone () : Statement {
return new ExpressionStatement(this._expr.clone());
}
override function serialize () : variant {
return [
"ExpressionStatement",
this._expr.serialize()
] : variant[];
}
}
class FunctionStatement extends Statement {
var _token : Token;
var _funcDef : MemberFunctionDefinition;
function constructor (token : Token, funcDef : MemberFunctionDefinition) {
super();
this._token = token;
this._funcDef = funcDef;
}
override function clone () : FunctionStatement {
// NOTE: funcDef is not cloned, but is later replaced in MemberFunctionDefitition#instantiate
return new FunctionStatement(this._token, this._funcDef);
}
override function getToken () : Token {
return this._token;
}
function getFuncDef () : MemberFunctionDefinition {
return this._funcDef;
}
function setFuncDef (funcDef : MemberFunctionDefinition) : void {
this._funcDef = funcDef;
}
override function serialize () : variant {
return [
"FunctionStatement",
this._funcDef.serialize()
] : variant[];
}
override function doAnalyze (context : AnalysisContext) : boolean {
if (! this._typesAreIdentified()) {
context.errors.push(new CompileError(this._token, "argument / return types were not automatically deductable, please specify them by hand"));
return false;
}
this._funcDef.analyze(context);
// the function can be used from the scope of the same level
context.getTopBlock().localVariableStatuses.setStatus(this._funcDef.getFuncLocal());
return true; // return true since everything is resolved correctly even if analysis of the function definition failed
}
function _typesAreIdentified () : boolean {
var argTypes = this._funcDef.getArgumentTypes();
for (var i = 0; i < argTypes.length; ++i) {
if (argTypes[i] == null)
return false;
}
if (this._funcDef.getReturnType() == null)
return false;
return true;
}
override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean {
return true;
}
}
class ReturnStatement extends Statement {
var _token : Token;
var _expr : Expression; // nullable
function constructor (token : Token, expr : Expression) {
super();
this._token = token;
this._expr = expr;
}
override function clone () : Statement {
return new ReturnStatement(this._token, Cloner.<Expression>.cloneNullable(this._expr));
}
override function getToken () : Token {
return this._token;
}
function getExpr () : Expression {
return this._expr;
}
function setExpr (expr : Expression) : void {
this._expr = expr;
}
override function serialize () : variant {
return [
"ReturnStatement",
Serializer.<Expression>.serializeNullable(this._expr)
] : variant[];
}
override function doAnalyze (context : AnalysisContext) : boolean {
// handle generator
if (context.funcDef.isGenerator()) {
context.errors.push(new CompileError(this._token, "return statement in generator is not allowed"));
return true;
}
var returnType = context.funcDef.getReturnType();
if (returnType == null) {
if (this._expr != null) {
if (! this._analyzeExpr(context, this._expr))
return true;
var exprType = this._expr.getType();
if (exprType == null)
return true;
context.funcDef.setReturnType(exprType);
} else {
context.funcDef.setReturnType(Type.voidType);
}
} else if (returnType.equals(Type.voidType)) {
// handle return(void);
if (this._expr != null) {
context.errors.push(new CompileError(this._token, "cannot return a value from a void function"));
return true;
}
} else {
// handle return of values
if (this._expr == null) {
context.errors.push(new CompileError(this._token, "cannot return void, the function is declared to return a value of type '" + returnType.toString() + "'"));
return true;
}
if (this._expr instanceof FunctionExpression && ! (this._expr as FunctionExpression).argumentTypesAreIdentified() && returnType instanceof StaticFunctionType) {
if (! (this._expr as FunctionExpression).deductTypeIfUnknown(context, returnType as StaticFunctionType))
return false;
}
if (! this._analyzeExpr(context, this._expr))
return true;
var exprType = this._expr.getType();
if (exprType == null)
return true;
if (! exprType.isConvertibleTo(returnType)) {
context.errors.push(new CompileError(this._token, "cannot convert '" + exprType.toString() + "' to return type '" + returnType.toString() + "'"));
return false;
}
}
context.getTopBlock().localVariableStatuses.setIsReachable(false);
return true;
}
override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean {
if (this._expr != null && ! cb(this._expr, function (expr) { this._expr = expr; }))
return false;
return true;
}
}
class YieldStatement extends Statement {
var _token : Token;
var _expr : Expression;
function constructor (token : Token, expr : Expression) {
super();
this._token = token;
this._expr = expr;
}
override function clone () : Statement {
return new YieldStatement(this._token, Cloner.<Expression>.cloneNullable(this._expr));
}
override function getToken () : Token {
return this._token;
}
function getExpr () : Expression {
return this._expr;
}
function setExpr (expr : Expression) : void {
this._expr = expr;
}
override function serialize () : variant {
return [
"YieldStatement",
Serializer.<Expression>.serializeNullable(this._expr)
] : variant[];
}
override function doAnalyze (context : AnalysisContext) : boolean {
// handle yield of values
if (! this._analyzeExpr(context, this._expr))
return true;
if (this._expr.getType() == null)
return true;
var returnType = context.funcDef.getReturnType();
if (returnType == null) {
var yieldType = this._expr.getType();
context.funcDef.setReturnType(new ObjectType(Util.instantiateTemplate(context, this._token, "g_Enumerable", [ yieldType ])));
} else {
if (returnType instanceof ObjectType
&& returnType.getClassDef() instanceof InstantiatedClassDefinition
&& (returnType.getClassDef() as InstantiatedClassDefinition).getTemplateClassName() == "g_Enumerable") {
yieldType = (returnType.getClassDef() as InstantiatedClassDefinition).getTypeArguments()[0];
} else {
// return type is not an instance of Enumerable. the error will be reported by MemberFuncitonDefinition#analyze.
context.errors.push(new CompileError(this._token, "cannot convert 'g_Enumerable.<" + this._expr.getType().toString() + ">' to return type '" + returnType.toString() + "'"));
return false;
}
}
if (! this._expr.getType().isConvertibleTo(yieldType)) {
context.errors.push(new CompileError(this._token, "cannot convert '" + this._expr.getType().toString() + "' to yield type '" + yieldType.toString() + "'"));
return false;
}
return true;
}
override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean {
if (this._expr != null && ! cb(this._expr, function (expr) { this._expr = expr; }))
return false;
return true;
}
}
class DeleteStatement extends UnaryExpressionStatement {
var _token : Token;
function constructor (token : Token, expr : Expression) {
super(expr);
this._token = token;
}
override function clone () : Statement {
return new DeleteStatement(this._token, this._expr.clone());
}
override function getToken () : Token {
return this._token;
}
override function serialize () : variant {
return [
"DeleteStatement",
this._expr.serialize()
] : variant[];
}
override function doAnalyze (context : AnalysisContext) : boolean {
if (! this._analyzeExpr(context, this._expr))
return true;
if (! (this._expr instanceof ArrayExpression)) {
context.errors.push(new CompileError(this._token, "only properties of a hash object can be deleted"));
return true;
}
var secondExprType = (this._expr as ArrayExpression).getSecondExpr().getType();
if (secondExprType == null)
return true; // error should have been already reported
if (! secondExprType.resolveIfNullable().equals(Type.stringType)) {
context.errors.push(new CompileError(this._token, "only properties of a hash object can be deleted"));
return true;
}
return true;
}
}
// break and continue
abstract class JumpStatement extends Statement {
var _token : Token;
var _label : Token;
function constructor (token : Token, label : Token) {
super();
this._token = token;
this._label = label;
}
override function getToken () : Token {
return this._token;
}
abstract function _getName() : string;
function getLabel () : Token {
return this._label;
}
override function serialize () : variant {
return [
this._getName(),
this._token.serialize(),
Serializer.<Token>.serializeNullable(this._label)
] : variant[];
}
override function doAnalyze (context : AnalysisContext) : boolean {
var targetBlock = this._determineDestination(context);
if (targetBlock == null)
return true;
if (this instanceof BreakStatement)
(targetBlock.block as LabellableStatement).registerVariableStatusesOnBreak(context.getTopBlock().localVariableStatuses);
else
(targetBlock.block as ContinuableStatement).registerVariableStatusesOnContinue(context.getTopBlock().localVariableStatuses);
context.getTopBlock().localVariableStatuses.setIsReachable(false);
return true;
}
function _determineDestination (context : AnalysisContext) : BlockContext {
for (var i = context.blockStack.length - 1; ! (context.blockStack[i].block instanceof MemberFunctionDefinition); --i) {
var statement = context.blockStack[i].block;
// continue unless we are at the destination level
if (! (statement instanceof LabellableStatement))
continue;
if (this._label != null) {
var statementLabel = (statement as LabellableStatement).getLabel();
if (statementLabel != null && statementLabel.getValue() == this._label.getValue()) {
if (this._token.getValue() == "continue" && statement instanceof SwitchStatement) {
context.errors.push(new CompileError(this._token, "cannot 'continue' to a switch statement"));
return null;
}
} else {
continue;
}
} else {
if (this._token.getValue() == "continue" && statement instanceof SwitchStatement)
continue;
}
// found the destination
return context.blockStack[i];
}
if (this._label != null)
context.errors.push(new CompileError(this._label, "label '" + this._label.getValue() + "' is either not defined or invalid as the destination"));
else
context.errors.push(new CompileError(this._token, "cannot '" + this._token.getValue() + "' at this point"));
return null;
}
override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean {
return true;
}
}
class BreakStatement extends JumpStatement {
function constructor (token : Token, label : Token) {
super(token, label);
}
override function clone () : Statement {
return new BreakStatement(this._token, this._label);
}
override function _getName () : string {
return "BreakStatement";
}
override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean {
return true;
}
}
class ContinueStatement extends JumpStatement {
function constructor (token : Token, label : Token) {
super(token, label);
}
override function clone () : Statement {
return new ContinueStatement(this._token, this._label);
}
override function _getName () : string {
return "ContinueStatement";
}
override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean {
return true;
}
}
// control flow statements
abstract class LabellableStatement extends Statement implements Block {
var _token : Token;
var _label : Token;
var _lvStatusesOnBreak : LocalVariableStatuses;
function constructor (token : Token, label : Token) {
super();
this._token = token;
this._label = label;
}
override function getToken () : Token {
return this._token;
}
function getLabel () : Token {
return this._label;
}
function _serialize () : variant[] {
return [
Serializer.<Token>.serializeNullable(this._label)
] : variant[];
}
function _prepareBlockAnalysis (context : AnalysisContext) : void {
context.blockStack.push(new BlockContext(context.getTopBlock().localVariableStatuses.clone(), this));
this._lvStatusesOnBreak = context.getTopBlock().localVariableStatuses.clone();
this._lvStatusesOnBreak.setIsReachable(false);
}
function _abortBlockAnalysis (context : AnalysisContext) : void {
context.blockStack.pop();
this._lvStatusesOnBreak = null;
}
function _finalizeBlockAnalysis (context : AnalysisContext) : void {
context.blockStack.pop();
context.getTopBlock().localVariableStatuses = this._lvStatusesOnBreak;
this._lvStatusesOnBreak = null;
}
function registerVariableStatusesOnBreak (statuses : LocalVariableStatuses) : void {
if (statuses != null) {
if (this._lvStatusesOnBreak == null)
this._lvStatusesOnBreak = statuses.clone();
else
this._lvStatusesOnBreak = this._lvStatusesOnBreak.merge(statuses);
}
}
}
abstract class ContinuableStatement extends LabellableStatement {
var _statements : Statement[];
var _lvStatusesOnContinue : LocalVariableStatuses;
function constructor (token : Token, label : Token, statements : Statement[]) {
super(token, label);
this._statements = statements;
}
function getStatements () : Statement[] {
return this._statements;
}
override function forEachStatement (cb : function(:Statement):boolean) : boolean {
if (! Util.forEachStatement(cb, this._statements))
return false;
return true;
}
override function handleStatements (cb : function(:Statement[]):boolean) : boolean {
if (! cb(this._statements))
return false;
return true;
}
override function _prepareBlockAnalysis (context : AnalysisContext) : void {
super._prepareBlockAnalysis(context);
this._lvStatusesOnContinue = null;
}
override function _abortBlockAnalysis (context : AnalysisContext) : void {
super._abortBlockAnalysis(context);
this._lvStatusesOnContinue = null;
}
override function _finalizeBlockAnalysis (context : AnalysisContext) : void {
super._finalizeBlockAnalysis(context);
this._restoreContinueVariableStatuses(context);
}
function _restoreContinueVariableStatuses (context : AnalysisContext) : void {
if (this._lvStatusesOnContinue != null) {
context.getTopBlock().localVariableStatuses = context.getTopBlock().localVariableStatuses.merge(this._lvStatusesOnContinue);
this._lvStatusesOnContinue = null;
}
}
function registerVariableStatusesOnContinue (statuses : LocalVariableStatuses) : void {
if (statuses != null) {
if (this._lvStatusesOnContinue == null)
this._lvStatusesOnContinue = statuses.clone();
else
this._lvStatusesOnContinue = this._lvStatusesOnContinue.merge(statuses);
}
}
}
class DoWhileStatement extends ContinuableStatement {
var _expr : Expression;
function constructor (token : Token, label : Token, expr : Expression, statements : Statement[]) {
super(token, label, statements);
this._expr = expr;
}
override function clone () : Statement {
return new DoWhileStatement(this._token, this._label, this._expr.clone(), Cloner.<Statement>.cloneArray(this._statements));
}
function getExpr () : Expression {
return this._expr;
}
override function serialize () : variant {
return [
"DoWhileStatement"
] : variant[].concat(this._serialize()).concat([
this._expr.serialize(),
Serializer.<Statement>.serializeArray(this._statements)
]);
}
override function doAnalyze (context : AnalysisContext) : boolean {
this._prepareBlockAnalysis(context);
try {
for (var i = 0; i < this._statements.length; ++i)
if (! this._statements[i].analyze(context))
return false;
this._restoreContinueVariableStatuses(context);
if (! Statement.assertIsReachable(context, this._expr.getToken()))
return false;
if (this._analyzeExpr(context, this._expr))
if (this._expr.getType().resolveIfNullable().equals(Type.voidType))
context.errors.push(new CompileError(this._expr.getToken(), "expression of the do-while statement should not return void"));
this.registerVariableStatusesOnBreak(context.getTopBlock().localVariableStatuses);
this._finalizeBlockAnalysis(context);
} catch (e : Error) {
this._abortBlockAnalysis(context);
throw e;
}
return true;
}
override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean {
if (! cb(this._expr, function (expr) { this._expr = expr; }))
return false;
return true;
}
}
class ForInStatement extends ContinuableStatement {
var _lhsExpr : Expression;
var _listExpr : Expression;
function constructor (token : Token, label : Token, lhsExpr : Expression, listExpr : Expression, statements : Statement[]) {
super(token, label, statements);
this._lhsExpr = lhsExpr;
this._listExpr = listExpr;
}
override function clone () : Statement {
return new ForInStatement(this._token, this._label, this._lhsExpr.clone(), this._listExpr.clone(), Cloner.<Statement>.cloneArray(this._statements));
}
function getLHSExpr () : Expression {
return this._lhsExpr;
}
function getListExpr () : Expression {
return this._listExpr;
}
override function getStatements () : Statement[] {
return this._statements;
}
override function serialize () : variant {
return [
"ForInStatement"
] : variant[].concat(this._serialize()).concat([
this._lhsExpr.serialize(),
this._listExpr.serialize(),
Serializer.<Statement>.serializeArray(this._statements)
]);
}
override function doAnalyze (context : AnalysisContext) : boolean {
if (! this._analyzeExpr(context, this._listExpr))
return true;
var listType = this._listExpr.getType().resolveIfNullable();
var listClassDef;
var listTypeName;
if (listType instanceof ObjectType
&& (listClassDef = listType.getClassDef()) instanceof InstantiatedClassDefinition
&& ((listTypeName = (listClassDef as InstantiatedClassDefinition).getTemplateClassName()) == "Array" || listTypeName == "Map")) {
// ok
} else {
context.errors.push(new CompileError(this.getToken(), "list expression of the for..in statement should be an array or a map"));
return true;
}
this._prepareBlockAnalysis(context);
try {
this._analyzeExpr(context, this._lhsExpr);
if (! this._lhsExpr.assertIsAssignable(context, this._token, listTypeName == "Array" ? (Type.numberType as Type) : (Type.stringType as Type)))
return false;
for (var i = 0; i < this._statements.length; ++i)
if (! this._statements[i].analyze(context))
return false;
this.registerVariableStatusesOnContinue(context.getTopBlock().localVariableStatuses);
this._finalizeBlockAnalysis(context);
} catch (e : Error) {
this._abortBlockAnalysis(context);
throw e;
}
return true;
}
override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean {
if (! cb(this._lhsExpr, function (expr) { this._lhsExpr = expr; }))
return false;
if (! cb(this._listExpr, function (expr) { this._listExpr = expr; }))
return false;
return true;
}
}
class ForStatement extends ContinuableStatement {
var _initExpr : Expression;
var _condExpr : Expression;
var _postExpr : Expression;
function constructor (token : Token, label : Token, initExpr : Expression, condExpr : Expression, postExpr : Expression, statements : Statement[]) {
super(token, label, statements);
this._initExpr = initExpr; // nullable
this._condExpr = condExpr; // nullable
this._postExpr = postExpr; // nullable
}
override function clone () : Statement {
return new ForStatement(this._token, this._label, Cloner.<Expression>.cloneNullable(this._initExpr), Cloner.<Expression>.cloneNullable(this._condExpr), Cloner.<Expression>.cloneNullable(this._postExpr), Cloner.<Statement>.cloneArray(this._statements));
}
function getInitExpr () : Expression {
return this._initExpr;
}
function setInitExpr (expr : Expression) : void {
this._initExpr = expr;
}
function getCondExpr () : Expression {
return this._condExpr;
}
function getPostExpr () : Expression {
return this._postExpr;
}
override function getStatements () : Statement[] {
return this._statements;
}
override function serialize () : variant {
return [
"ForStatement"
] : variant[].concat(this._serialize()).concat([
Serializer.<Expression>.serializeNullable(this._initExpr),
Serializer.<Expression>.serializeNullable(this._condExpr),
Serializer.<Expression>.serializeNullable(this._postExpr),
Serializer.<Statement>.serializeArray(this._statements)
]);
}
override function doAnalyze (context : AnalysisContext) : boolean {
if (this._initExpr != null)
this._analyzeExpr(context, this._initExpr);
if (this._condExpr != null)
if (this._analyzeExpr(context, this._condExpr))
if (this._condExpr.getType().resolveIfNullable().equals(Type.voidType))
context.errors.push(new CompileError(this._condExpr.getToken(), "condition expression of the for statement should not return void"));
this._prepareBlockAnalysis(context);
try {
for (var i = 0; i < this._statements.length; ++i)
if (! this._statements[i].analyze(context))
return false;
this._restoreContinueVariableStatuses(context);
if (this._postExpr != null) {
if (! Statement.assertIsReachable(context, this._postExpr.getToken()))
return false;
this._analyzeExpr(context, this._postExpr);
}
this.registerVariableStatusesOnBreak(context.getTopBlock().localVariableStatuses);
this._finalizeBlockAnalysis(context);
} catch (e : Error) {
this._abortBlockAnalysis(context);
throw e;
}
return true;
}
override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean {
if (this._initExpr != null && ! cb(this._initExpr, function (expr) { this._initExpr = expr; }))
return false;
if (this._condExpr != null && ! cb(this._condExpr, function (expr) { this._condExpr = expr; }))
return false;
if (this._postExpr != null && ! cb(this._postExpr, function (expr) { this._postExpr = expr; }))
return false;
return true;
}
}
class IfStatement extends Statement implements Block {
var _token : Token;
var _expr : Expression;
var _onTrueStatements : Statement[];
var _onFalseStatements : Statement[];
function constructor (token : Token, expr : Expression, onTrueStatements : Statement[], onFalseStatements : Statement[]) {
super();
this._token = token;
this._expr = expr;
this._onTrueStatements = onTrueStatements;
this._onFalseStatements = onFalseStatements;
}
override function clone () : Statement {
return new IfStatement(this._token, this._expr.clone(), Cloner.<Statement>.cloneArray(this._onTrueStatements), Cloner.<Statement>.cloneArray(this._onFalseStatements));
}
override function getToken () : Token {
return this._token;
}
function getExpr () : Expression {
return this._expr;
}
function setExpr (expr : Expression) : void {
this._expr = expr;
}
function getOnTrueStatements () : Statement[] {
return this._onTrueStatements;
}
function getOnFalseStatements () : Statement[] {
return this._onFalseStatements;
}
override function serialize () : variant {
return [
"IfStatement",
this._expr.serialize(),
Serializer.<Statement>.serializeArray(this._onTrueStatements),
Serializer.<Statement>.serializeArray(this._onFalseStatements)
] : variant[];
}
override function doAnalyze (context : AnalysisContext) : boolean {
if (this._analyzeExpr(context, this._expr))
if (this._expr.getType().resolveIfNullable().equals(Type.voidType))
context.errors.push(new CompileError(this._expr.getToken(), "expression of the if statement should not return void"));
// if the expr is true
context.blockStack.push(new BlockContext(context.getTopBlock().localVariableStatuses.clone(), this));
var lvStatusesOnTrueStmts = null : LocalVariableStatuses, lvStatusesOnFalseStmts = null : LocalVariableStatuses;
try {
for (var i = 0; i < this._onTrueStatements.length; ++i)
if (! this._onTrueStatements[i].analyze(context))
return false;
lvStatusesOnTrueStmts = context.getTopBlock().localVariableStatuses;
} finally {
context.blockStack.pop();
}
// if the expr is false
try {
context.blockStack.push(new BlockContext(context.getTopBlock().localVariableStatuses.clone(), this));
for (var i = 0; i < this._onFalseStatements.length; ++i)
if (! this._onFalseStatements[i].analyze(context))
return false;
var lvStatusesOnFalseStmts = context.getTopBlock().localVariableStatuses;
} finally {
context.blockStack.pop();
}
// merge the variable statuses
context.getTopBlock().localVariableStatuses = lvStatusesOnTrueStmts.merge(lvStatusesOnFalseStmts);
return true;
}
override function forEachStatement (cb : function(:Statement):boolean) : boolean {
if (! Util.forEachStatement(cb, this._onTrueStatements))
return false;
if (! Util.forEachStatement(cb, this._onFalseStatements))
return false;
return true;
}
override function handleStatements (cb : function(:Statement[]):boolean) : boolean {
if (! cb(this._onTrueStatements))
return false;
if (! cb(this._onFalseStatements))
return false;
return true;
}
override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean {
if (! cb(this._expr, function (expr) { this._expr = expr; }))
return false;
return true;
}
}
class SwitchStatement extends LabellableStatement {
var _expr : Expression;
var _statements : Statement[];
function constructor (token : Token, label : Token, expr : Expression, statements : Statement[]) {
super(token, label);
this._expr = expr;
this._statements = statements;
}
override function clone () : Statement {
return new SwitchStatement(this._token, this._label, this._expr.clone(), Cloner.<Statement>.cloneArray(this._statements));
}
function getExpr () : Expression {
return this._expr;
}
function setExpr (expr : Expression) : void {
this._expr = expr;
}
function getStatements () : Statement[] {
return this._statements;
}
override function serialize () : variant {
return [
"SwitchStatement"
] : variant[].concat(this._serialize()).concat([
this._expr.serialize(),
Serializer.<Statement>.serializeArray(this._statements)
]);
}
override function doAnalyze (context : AnalysisContext) : boolean {
if (! this._analyzeExpr(context, this._expr))
return true;
var exprType = this._expr.getType().resolveIfNullable();
if (! (exprType.equals(Type.booleanType) || exprType.equals(Type.integerType) || exprType.equals(Type.numberType) || exprType.equals(Type.stringType))) {
context.errors.push(new CompileError(this._token, "switch statement only accepts boolean, number, or string expressions"));
return true;
}
this._prepareBlockAnalysis(context);
try {
var hasDefaultLabel = false;
for (var i = 0; i < this._statements.length; ++i) {
var statement = this._statements[i];
if (! statement.analyze(context))
return false;
if (statement instanceof DefaultStatement)
hasDefaultLabel = true;
}
if (context.getTopBlock().localVariableStatuses.isReachable())
this.registerVariableStatusesOnBreak(context.getTopBlock().localVariableStatuses);
if (! hasDefaultLabel)
this.registerVariableStatusesOnBreak(context.blockStack[context.blockStack.length - 2].localVariableStatuses);
this._finalizeBlockAnalysis(context);
} catch (e : Error) {
this._abortBlockAnalysis(context);
throw e;
}
return true;
}
override function forEachStatement (cb : function(:Statement):boolean) : boolean {
if (! Util.forEachStatement(cb, this._statements))
return false;
return true;
}
override function handleStatements (cb : function(:Statement[]):boolean) : boolean {
if (! cb(this._statements))
return false;
return true;
}
override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean {
if (! cb(this._expr, function (expr) { this._expr = expr; }))
return false;
return true;
}
static function resetLocalVariableStatuses (context : AnalysisContext) : void {
context.getTopBlock().localVariableStatuses = context.blockStack[context.blockStack.length - 2].localVariableStatuses.clone();
}
}
class CaseStatement extends Statement {
var _token : Token;
var _expr : Expression;
function constructor (token : Token, expr : Expression) {
super();
this._token = token;
this._expr = expr;
}
override function clone () : Statement {
return new CaseStatement(this._token, this._expr.clone());
}
override function getToken () : Token {
return this._token;
}
function getExpr () : Expression {
return this._expr;
}
override function serialize () : variant {
return [
"CaseStatement",
this._expr.serialize()
] : variant[];
}
override function doAnalyze (context : AnalysisContext) : boolean {
if (! this._analyzeExpr(context, this._expr))
return true;
var statement = context.getTopBlock().block;
if (! (statement instanceof SwitchStatement))
throw new Error("logic flaw");
var expectedType = (statement as SwitchStatement).getExpr().getType();
if (expectedType == null)
return true;
expectedType = expectedType.resolveIfNullable();
var exprType = this._expr.getType();
if (exprType == null)
return true;
exprType = exprType.resolveIfNullable();
if (exprType.equals(expectedType)) {
// ok
} else if (Type.isIntegerOrNumber(exprType) && Type.isIntegerOrNumber(expectedType)) {
// ok
} else if (expectedType.equals(Type.stringType) && exprType.equals(Type.nullType)) {
// ok
} else {
context.errors.push(new CompileError(this._token, "type mismatch; expected type was '" + expectedType.toString() + "' but got '" + exprType.toString() + "'"));
}
// reset local variable statuses
SwitchStatement.resetLocalVariableStatuses(context);
return true;
}
override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean {
if (! cb(this._expr, function (expr) { this._expr = expr; }))
return false;
return true;
}
}
class DefaultStatement extends Statement {
var _token : Token;
function constructor (token : Token) {
super();
this._token = token;
}
override function clone () : Statement {
return new DefaultStatement(this._token);
}
override function getToken () : Token {
return this._token;
}
override function serialize () : variant {
return [
"DefaultStatement"
] : variant[];
}
override function doAnalyze (context : AnalysisContext) : boolean {
SwitchStatement.resetLocalVariableStatuses(context);
return true;
}
override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean {
return true;
}
}
class WhileStatement extends ContinuableStatement {
var _expr : Expression;
function constructor (token : Token, label : Token, expr : Expression, statements : Statement[]) {
super(token, label, statements);
this._expr = expr;
}
override function clone () : Statement {
return new WhileStatement(this._token, this._label, this._expr.clone(), Cloner.<Statement>.cloneArray(this._statements));
}
function getExpr () : Expression {
return this._expr;
}
override function getStatements () : Statement[] {
return this._statements;
}
override function serialize () : variant {
return [
"WhileStatement"
] : variant[].concat(this._serialize()).concat([
this._expr.serialize(),
Serializer.<Statement>.serializeArray(this._statements)
]);
}
override function doAnalyze (context : AnalysisContext) : boolean {
if (this._analyzeExpr(context, this._expr))
if (this._expr.getType().resolveIfNullable().equals(Type.voidType))
context.errors.push(new CompileError(this._expr.getToken(), "expression of the while statement should not return void"));
this._prepareBlockAnalysis(context);
try {
for (var i = 0; i < this._statements.length; ++i)
if (! this._statements[i].analyze(context))
return false;
this.registerVariableStatusesOnContinue(context.getTopBlock().localVariableStatuses);
this._finalizeBlockAnalysis(context);
} catch (e : Error) {
this._abortBlockAnalysis(context);
throw e;
}
return true;
}
override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean {
if (! cb(this._expr, function (expr) { this._expr = expr; }))
return false;
return true;
}
}
class TryStatement extends Statement implements Block {
var _token : Token;
var _tryStatements : Statement[];
var _catchStatements : CatchStatement[];
var _finallyStatements : Statement[];
function constructor (token : Token, tryStatements : Statement[], catchStatements : CatchStatement[], finallyStatements : Statement[]) {
super();
this._token = token;
this._tryStatements = tryStatements;
this._catchStatements = catchStatements;
this._finallyStatements = finallyStatements;
}
override function clone () : Statement {
return new TryStatement(this._token, Cloner.<Statement>.cloneArray(this._tryStatements), Cloner.<CatchStatement>.cloneArray(this._catchStatements), Cloner.<Statement>.cloneArray(this._finallyStatements));
}
override function getToken () : Token {
return this._token;
}
function getTryStatements () : Statement[] {
return this._tryStatements;
}
function getCatchStatements () : CatchStatement[] {
return this._catchStatements;
}
function getFinallyStatements () : Statement[] {
return this._finallyStatements;
}
override function serialize () : variant {
return [
"TryStatement",
Serializer.<Statement>.serializeArray(this._tryStatements),
Serializer.<CatchStatement>.serializeArray(this._catchStatements),
Serializer.<Statement>.serializeArray(this._finallyStatements)
] : variant[];
}
override function doAnalyze (context : AnalysisContext) : boolean {
if ((context.funcDef.flags() & ClassDefinition.IS_GENERATOR) != 0) {
context.errors.push(new CompileError(this._token, "invalid use of try block inside generator"));
return false;
}
// try
context.blockStack.push(new BlockContext(context.getTopBlock().localVariableStatuses.clone(), this));
var lvStatusesAfterTryCatch = null : LocalVariableStatuses;
try {
for (var i = 0; i < this._tryStatements.length; ++i)
if (! this._tryStatements[i].analyze(context))
return false;
lvStatusesAfterTryCatch = context.getTopBlock().localVariableStatuses;
} finally {
context.blockStack.pop();
}
// catch
for (var i = 0; i < this._catchStatements.length; ++i) {
context.blockStack.push(new BlockContext(context.getTopBlock().localVariableStatuses.clone(), this._catchStatements[i]));
var lvStatusesAfterCatch = null : LocalVariableStatuses;
try {
if (! this._catchStatements[i].analyze(context))
return false;
lvStatusesAfterCatch = context.getTopBlock().localVariableStatuses;
} finally {
context.blockStack.pop();
}
lvStatusesAfterTryCatch = lvStatusesAfterTryCatch.merge(lvStatusesAfterCatch);
var curCatchType = this._catchStatements[i].getLocal().getType();
for (var j = 0; j < i; ++j) {
var precCatchType = this._catchStatements[j].getLocal().getType();
if (curCatchType.isConvertibleTo(precCatchType)) {
context.errors.push(new CompileError(
this._catchStatements[i]._token,
"code is unreachable, a broader catch statement for type '" + precCatchType.toString() + "' already exists"));
return false;
}
}
}
// finally
context.blockStack.push(new BlockContext(context.getTopBlock().localVariableStatuses.merge(lvStatusesAfterTryCatch), this));
var lvStatusesAfterFinally = null : LocalVariableStatuses;
try {
for (var i = 0; i < this._finallyStatements.length; ++i)
if (! this._finallyStatements[i].analyze(context))
return false;
lvStatusesAfterFinally = context.getTopBlock().localVariableStatuses;
} finally {
context.blockStack.pop();
}
context.getTopBlock().localVariableStatuses = lvStatusesAfterTryCatch.mergeFinally(lvStatusesAfterFinally);
return true;
}
override function forEachStatement (cb : function(:Statement):boolean) : boolean {
if (! Util.forEachStatement(cb, this._tryStatements))
return false;
if (! Util.forEachStatement(cb, this._catchStatements.map.<Statement>((s) -> { return s; })))
return false;
if (! Util.forEachStatement(cb, this._finallyStatements))
return false;
return true;
}
override function handleStatements (cb : function(:Statement[]):boolean) : boolean {
if (! cb(this._tryStatements))
return false;
if (! cb(this._catchStatements.map.<Statement>((s) -> { return s; })))
return false;
if (! cb(this._finallyStatements))
return false;
return true;
}
override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean {
return true;
}
}
class CatchStatement extends Statement implements Block {
var _token : Token;
var _local : CaughtVariable;
var _statements : Statement[];
function constructor (token : Token, local : CaughtVariable, statements : Statement[]) {
super();
this._token = token;
this._local = local;
this._statements = statements;
}
override function clone () : Statement {
// TODO rewrite the references from _statements to _local
return new CatchStatement(this._token, this._local, Cloner.<Statement>.cloneArray(this._statements));
}
override function getToken () : Token {
return this._token;
}
function getLocal () : CaughtVariable {
return this._local;
}
function setLocal (local : CaughtVariable) : void {
// NOTE: does not rewrite the references to the local from the statements within
this._local = local;
}
function getStatements () : Statement[] {
return this._statements;
}
override function serialize () : variant {
return [
"CatchStatement",
this._token.serialize(),
this._local.serialize(),
Serializer.<Statement>.serializeArray(this._statements)
] : variant[];
}
override function doAnalyze (context : AnalysisContext) : boolean {
// check the catch type
var catchType = this.getLocal().getType();
if (! (catchType instanceof ObjectType || catchType.equals(Type.variantType))) {
context.errors.push(new CompileError(this._token, "only objects or a variant may be caught"));
}
// analyze the statements
for (var i = 0; i < this._statements.length; ++i) {
if (! this._statements[i].analyze(context))
return false;
}
return true;
}
override function forEachStatement (cb : function(:Statement):boolean) : boolean {
return Util.forEachStatement(cb, this._statements);
}
override function handleStatements (cb : function(:Statement[]):boolean) : boolean {
return cb(this._statements);
}
override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean {
return true;
}
}
class ThrowStatement extends Statement {
var _token : Token;
var _expr : Expression;
function constructor (token : Token, expr : Expression) {
super();
this._token = token;
this._expr = expr;
}
override function clone () : Statement {
return new ThrowStatement(this._token, this._expr.clone());
}
override function getToken () : Token {
return this._token;
}
function getExpr () : Expression {
return this._expr;
}
override function serialize () : variant {
return [
"ThrowStatement",
this._token.serialize(),
this._expr.serialize()
] : variant[];
}
override function doAnalyze (context : AnalysisContext) : boolean {
if (! this._analyzeExpr(context, this._expr))
return true;
var errorClassDef = context.parser.lookup(context.errors, this._token, "Error");
if (errorClassDef == null)
throw new Error("could not find definition for Error");
if (this._expr.getType().equals(Type.voidType)) {
context.errors.push(new CompileError(this._token, "cannot throw 'void'"));
return true;
}
context.getTopBlock().localVariableStatuses.setIsReachable(false);
return true;
}
override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean {
if (! cb(this._expr, function (expr) { this._expr = expr; }))
return false;
return true;
}
}
// information statements
abstract class InformationStatement extends Statement {
var _token : Token;
function constructor (token : Token) {
super();
this._token = token;
}
override function getToken () : Token {
return this._token;
}
}
class AssertStatement extends InformationStatement {
var _expr : Expression;
function constructor (token : Token, expr : Expression) {
super(token);
this._expr = expr;
}
override function clone () : Statement {
return new AssertStatement(this._token, this._expr.clone());
}
function getExpr () : Expression {
return this._expr;
}
override function serialize () : variant {
return [
"AssertStatement",
this._token.serialize(),
Serializer.<Expression>.serializeNullable(this._expr)
] : variant[];
}
override function doAnalyze (context : AnalysisContext) : boolean {
if (! this._analyzeExpr(context, this._expr))
return true;
var exprType = this._expr.getType();
if (exprType.equals(Type.voidType))
context.errors.push(new CompileError(this._expr.getToken(), "argument of the assert statement cannot be void"));
return true;
}
override function forEachExpression (cb : function(:Expression,:function(:Expression):void):boolean) : boolean {
if (! cb(this._expr, function (expr) { this._expr = expr; }))
return false;
return true;
}
}
class LogStatement extends InformationStatement {
var _exprs : Expression[];
function constructor (token : Token, exprs : Expression[]) {
super(token);
this._exprs = exprs;
}
override function clone () : Statement {
return new LogStatement(this._token, Cloner.<Expression>.cloneArray(this._exprs));
}
function getExprs () : Expression[] {
return this._exprs;
}
override function serialize () : variant {
return [
"LogStatement",
this._token.serialize(),
Serializer.<Expression>.serializeArray(this._exp