jsx
Version:
a faster, safer, easier JavaScript
534 lines (424 loc) • 14.8 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 "./classdef.jsx";
import "./parser.jsx";
import "./type.jsx";
import "./platform.jsx";
import "./statement.jsx";
import "./optimizer.jsx";
import "./util.jsx";
class InstantiationContext {
var errors : CompileError[];
var typemap : Map.<Type>;
var objectTypesUsed : ParsedObjectType[];
function constructor (errors : CompileError[], typemap : Map.<Type>) {
this.errors = errors;
this.typemap = typemap;
this.objectTypesUsed = new ParsedObjectType[];
}
}
class TemplateInstantiationRequest {
var _token : Token;
var _className : string;
var _typeArgs : Type[];
function constructor (token : Token, className : string, typeArgs : Type[]) {
this._token = token;
this._className = className;
this._typeArgs = typeArgs;
}
function getToken () : Token {
return this._token;
}
function getClassName () : string {
return this._className;
}
function getTypeArguments () : Type[] {
return this._typeArgs;
}
}
interface Block {
}
class BlockContext {
var localVariableStatuses : LocalVariableStatuses;
var block : Block;
function constructor (localVariableStatuses : LocalVariableStatuses, block : Block) {
this.localVariableStatuses = localVariableStatuses;
this.block = block;
}
}
class AnalysisContext {
var errors : CompileError[];
var parser : Parser;
var postInstantiationCallback : function(:Parser,:ClassDefinition):ClassDefinition;
var funcDef : MemberFunctionDefinition;
var blockStack : BlockContext[];
var statement : Statement;
function constructor (errors : CompileError[], parser : Parser, postInstantiationCallback : function(:Parser,:ClassDefinition):ClassDefinition) {
this.errors = errors;
this.parser = parser;
this.postInstantiationCallback = postInstantiationCallback;
this.funcDef = null;
/*
blockStack is a stack of blocks:
function f() { // pushes [ localVariableStatutes, funcDef ]
...
for (...) { // pushes [ localVariableStatuses, forStatement ]
...
}
try { // pushes [ localVariableStatuses, tryStatement ]
...
} catch (e : Error) { // pushes [ localVariableStatuses, catchStatement ]
...
function () { // pushes [ localVariableStatuses, funcDef ]
...
}
}
}
*/
this.blockStack = null;
this.statement = null;
}
function clone () : AnalysisContext {
// NOTE: does not clone the blockStack (call setBlockStack)
return new AnalysisContext(this.errors, this.parser, this.postInstantiationCallback).setFuncDef(this.funcDef);
}
function setFuncDef (funcDef : MemberFunctionDefinition) : AnalysisContext {
this.funcDef = funcDef;
return this;
}
function setBlockStack (stack : BlockContext[]) : AnalysisContext {
this.blockStack = stack;
return this;
}
function getTopBlock () : BlockContext {
return this.blockStack[this.blockStack.length - 1];
}
}
// stash to hold meta data used by code transformation, optimization, and emission
abstract class Stash {
abstract function clone () : Stash;
}
mixin Stashable {
var _stash = new Map.<Stash>;
function setStash (id : string, stash : Stash) : Stash {
return this._stash[id] = stash;
}
function getStash (id : string) : Stash {
return this._stash[id];
}
}
class LocalVariable implements Stashable {
var _name : Token;
var _type : Type;
var _instantiated : LocalVariable[];
var isInstantiated = false;
function constructor (name : Token, type : Type) {
this._name = name;
this._type = type;
this._instantiated = new LocalVariable[];
}
function serialize () : variant {
return [
this._name,
Serializer.<Type>.serializeNullable(this._type)
] : variant[];
}
function getName () : Token {
return this._name;
}
function getType () : Type {
return this._type;
}
function setType (type : Type) : void {
if (this._type != null)
throw new Error("type is already set for " + this.toString());
// implicit declarations of "int" is not supported
if (type.equals(Type.integerType))
type = Type.numberType;
this._type = type;
}
function setTypeForced (type : Type) : void {
this._type = type;
}
function touchVariable (context : AnalysisContext, token : Token, isAssignment : boolean) : boolean {
if (isAssignment) {
context.getTopBlock().localVariableStatuses.setStatus(this);
} else {
switch (context.getTopBlock().localVariableStatuses.getStatus(this)) {
case LocalVariableStatuses.UNTYPED_RECURSIVE_FUNCTION:
context.errors.push(new CompileError(token, "the return type of recursive function needs to be explicitly declared"));
return false;
case LocalVariableStatuses.ISSET:
// ok
break;
case LocalVariableStatuses.UNSET:
context.errors.push(new CompileError(token, "variable is not initialized"));
return false;
case LocalVariableStatuses.MAYBESET:
context.errors.push(new CompileError(token, "variable may not be initialized"));
return false;
default:
throw new Error("logic flaw");
}
}
return true;
}
override function toString () : string {
return this._name.getValue() + " : " + this._type.toString();
}
function popInstantiated () : void {
this._instantiated.pop();
}
function getInstantiated () : LocalVariable {
if (this._instantiated.length == 0) {
throw new Error("logic flaw, no instantiation for " + this._name.getValue() + "," + this.isInstantiated as string);
}
return this._instantiated[this._instantiated.length - 1];
}
function instantiateAndPush (instantiationContext : InstantiationContext) : LocalVariable {
var instantiated = this._instantiate(instantiationContext);
instantiated.isInstantiated = true;
this._instantiated.push(instantiated);
return instantiated;
}
function _instantiate (instantiationContext : InstantiationContext) : LocalVariable {
var type = this._type != null ? this._type.instantiate(instantiationContext) : null;
return new LocalVariable(this._name, type);
}
}
class CaughtVariable extends LocalVariable {
function constructor (name : Token, type : Type) {
super(name, type);
}
function clone () : CaughtVariable {
return new CaughtVariable(this._name, this._type);
}
override function touchVariable (context : AnalysisContext, token : Token, isAssignment : boolean) : boolean {
return true;
}
override function _instantiate (instantiationContext : InstantiationContext) : CaughtVariable {
return new CaughtVariable(this._name, this._type.instantiate(instantiationContext));
}
override function instantiateAndPush (instantiationContext : InstantiationContext) : CaughtVariable {
return super.instantiateAndPush(instantiationContext) as CaughtVariable;
}
}
class ArgumentDeclaration extends LocalVariable {
function constructor (name : Token, type : Type) {
super(name, type);
}
function clone () : ArgumentDeclaration {
return new ArgumentDeclaration(this._name, this._type);
}
override function _instantiate (instantiationContext : InstantiationContext) : ArgumentDeclaration {
var type = this._type != null ? this._type.instantiate(instantiationContext) : null;
return new ArgumentDeclaration(this._name, type);
}
override function instantiateAndPush (instantiationContext : InstantiationContext) : ArgumentDeclaration {
return super.instantiateAndPush(instantiationContext) as ArgumentDeclaration;
}
}
class LocalVariableStatuses {
static const UNTYPED_RECURSIVE_FUNCTION = -1;
static const UNSET = 0;
static const ISSET = 1;
static const MAYBESET = 2;
var _statuses : Map.<number>;
var _isReachable : boolean;
function constructor (funcDef : MemberFunctionDefinition, base : LocalVariableStatuses) {
this._statuses = new Map.<number>;
if (base != null) {
// FIXME the analysis of the closures should be delayed to either of: first being used, or return is called, to minimize the appearance of the "not initialized" error
for (var k in base._statuses)
this._statuses[k] = base._statuses[k] == LocalVariableStatuses.UNSET ? LocalVariableStatuses.MAYBESET : base._statuses[k] as number;
}
var args = funcDef.getArguments();
for (var i = 0; i < args.length; ++i)
this._statuses[args[i].getName().getValue()] = LocalVariableStatuses.ISSET;
var locals = funcDef.getLocals();
for (var i = 0; i < locals.length; ++i)
this._statuses[locals[i].getName().getValue()] = LocalVariableStatuses.UNSET;
this._isReachable = true;
}
function constructor (srcStatus : LocalVariableStatuses) {
this._statuses = new Map.<number>;
this._copyFrom(srcStatus);
this._isReachable = srcStatus._isReachable;
}
function clone () : LocalVariableStatuses {
return new LocalVariableStatuses(this);
}
function merge (that : LocalVariableStatuses) : LocalVariableStatuses {
if (this._isReachable != that._isReachable) {
if (this._isReachable) {
return this.clone();
} else {
return that.clone();
}
}
var ret = this.clone();
for (var k in ret._statuses) {
if (ret._statuses[k] == LocalVariableStatuses.UNSET && that._statuses[k] == LocalVariableStatuses.UNSET) {
// UNSET
} else if (ret._statuses[k] == LocalVariableStatuses.ISSET && that._statuses[k] == LocalVariableStatuses.ISSET) {
// ISSET
} else {
// MAYBESET
ret._statuses[k] = LocalVariableStatuses.MAYBESET;
}
}
return ret;
}
function mergeFinally (postFinallyStats : LocalVariableStatuses) : LocalVariableStatuses {
var ret = this.clone();
for (var k in ret._statuses) {
switch (postFinallyStats._statuses[k]) {
case LocalVariableStatuses.ISSET:
ret._statuses[k] = LocalVariableStatuses.ISSET;
break;
case LocalVariableStatuses.MAYBESET:
if (ret._statuses[k] != LocalVariableStatuses.ISSET) {
ret._statuses[k] = LocalVariableStatuses.MAYBESET;
}
break;
}
}
if (! postFinallyStats._isReachable) {
ret._isReachable = false;
}
return ret;
}
function setStatus (local : LocalVariable) : void {
var name = local.getName().getValue();
if (this._statuses[name] == null)
throw new Error("logic flaw, could not find status for local variable: " + name);
this._statuses[name] = LocalVariableStatuses.ISSET;
}
function getStatus (local : LocalVariable) : number {
var name = local.getName().getValue();
if (this._statuses[name] == null)
throw new Error("logic flaw, could not find status for local variable: " + name);
return this._statuses[name];
}
function isReachable() : boolean {
return this._isReachable;
}
function setIsReachable(isReachable : boolean) : void {
this._isReachable = isReachable;
}
function _copyFrom (that : LocalVariableStatuses) : void {
for (var k in that._statuses)
this._statuses[k] = that._statuses[k];
}
}
abstract class CompileIssue {
var _filename : Nullable.<string>;
var _lineNumber : number;
var _columnNumber : number;
var _message : string;
var _size : number;
function constructor (token : Token, message : string) {
if(token != null) {
this._filename = token.getFilename();
this._lineNumber = token.getLineNumber();
this._columnNumber = token.getColumnNumber();
// FIXME: deal with visual width
this._size = token.getValue().length;
this._message = message;
}
else {
this._filename = null;
this._lineNumber = 0;
this._columnNumber = -1;
this._message = message;
this._size = 1;
}
}
function constructor (filename : string, lineNumber : number, columnNumber : number, message : string) {
this._filename = filename;
this._lineNumber = lineNumber;
this._columnNumber = columnNumber;
this._message = message;
this._size = 1;
}
function format (platform : Platform) : string {
return Util.makeErrorMessage(platform, this.getPrefix() + this._message, this._filename, this._lineNumber, this._columnNumber, this._size);
}
abstract function getPrefix () : string;
}
class CompileError extends CompileIssue {
var _notes : CompileNote[];
function constructor (token : Token, message : string) {
super(token, message);
this._notes = new CompileNote[];
}
function constructor (filename : string, lineNumber : number, columnNumber : number, message : string) {
super(filename, lineNumber, columnNumber, message);
this._notes = new CompileNote[];
}
function addCompileNote (note : CompileNote) : CompileError {
this._notes.push(note);
return this;
}
function addCompileNotes (notes : CompileNote[]) : void {
notes.forEach( (note) -> {
this.addCompileNote(note);
});
}
function getCompileNotes () : CompileNote[] {
return this._notes;
}
override function getPrefix () : string {
return "";
}
}
class CompileWarning extends CompileError {
function constructor (token : Token, message : string) {
super(token, message);
}
function constructor (filename : string, lineNumber : number, columnNumber : number, message : string) {
super(filename, lineNumber, columnNumber, message);
}
override function getPrefix () : string {
return "Warning: ";
}
}
class DeprecatedWarning extends CompileWarning {
function constructor (token : Token, message : string) {
super(token, message);
}
function constructor (filename : string, lineNumber : number, columnNumber : number, message : string) {
super(filename, lineNumber, columnNumber, message);
}
}
class CompileNote extends CompileIssue {
function constructor (token : Token, message : string) {
super(token, message);
}
function constructor (filename : string, lineNumber : number, columnNumber : number, message : string) {
super(filename, lineNumber, columnNumber, message);
}
override function getPrefix () : string {
return "Note: ";
}
}
// vim: set noexpandtab: