mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-06-13 23:11:41 +00:00
Support parsing of class expressions, see #161
This commit is contained in:
2
dist/assemblyscript.js
vendored
2
dist/assemblyscript.js
vendored
File diff suppressed because one or more lines are too long
2
dist/assemblyscript.js.map
vendored
2
dist/assemblyscript.js.map
vendored
File diff suppressed because one or more lines are too long
18
src/ast.ts
18
src/ast.ts
@ -41,6 +41,7 @@ export enum NodeKind {
|
||||
ASSERTION,
|
||||
BINARY,
|
||||
CALL,
|
||||
CLASS,
|
||||
COMMA,
|
||||
ELEMENTACCESS,
|
||||
FALSE,
|
||||
@ -320,6 +321,15 @@ export abstract class Node {
|
||||
return expr;
|
||||
}
|
||||
|
||||
static createClassExpression(
|
||||
declaration: ClassDeclaration
|
||||
): ClassExpression {
|
||||
var expr = new ClassExpression();
|
||||
expr.range = declaration.range;
|
||||
expr.declaration = declaration;
|
||||
return expr;
|
||||
}
|
||||
|
||||
static createCommaExpression(
|
||||
expressions: Expression[],
|
||||
range: Range
|
||||
@ -1276,6 +1286,14 @@ export class CallExpression extends Expression {
|
||||
arguments: Expression[];
|
||||
}
|
||||
|
||||
/** Represents a class expression using the 'class' keyword. */
|
||||
export class ClassExpression extends Expression {
|
||||
kind = NodeKind.CLASS;
|
||||
|
||||
/** Inline class declaration. */
|
||||
declaration: ClassDeclaration;
|
||||
}
|
||||
|
||||
/** Represents a comma expression composed of multiple expressions. */
|
||||
export class CommaExpression extends Expression {
|
||||
kind = NodeKind.COMMA;
|
||||
|
@ -2379,7 +2379,10 @@ export class Compiler extends DiagnosticEmitter {
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
assert(false);
|
||||
this.error(
|
||||
DiagnosticCode.Operation_not_supported,
|
||||
expression.range
|
||||
);
|
||||
expr = this.module.createUnreachable();
|
||||
}
|
||||
}
|
||||
|
@ -76,7 +76,8 @@ import {
|
||||
ParameterNode,
|
||||
ParameterKind,
|
||||
ExportMember,
|
||||
SwitchCase
|
||||
SwitchCase,
|
||||
ClassExpression
|
||||
} from "../ast";
|
||||
|
||||
import {
|
||||
@ -147,6 +148,10 @@ export class ASTBuilder {
|
||||
this.visitCallExpression(<CallExpression>node);
|
||||
break;
|
||||
}
|
||||
case NodeKind.CLASS: {
|
||||
this.visitClassExpression(<ClassExpression>node);
|
||||
break;
|
||||
}
|
||||
case NodeKind.COMMA: {
|
||||
this.visitCommaExpression(<CommaExpression>node);
|
||||
break;
|
||||
@ -477,6 +482,11 @@ export class ASTBuilder {
|
||||
sb.push(")");
|
||||
}
|
||||
|
||||
visitClassExpression(node: ClassExpression): void {
|
||||
var declaration = node.declaration;
|
||||
this.visitClassDeclaration(declaration);
|
||||
}
|
||||
|
||||
visitCommaExpression(node: CommaExpression): void {
|
||||
var expressions = node.expressions;
|
||||
var numExpressions = assert(expressions.length);
|
||||
@ -719,9 +729,10 @@ export class ASTBuilder {
|
||||
sb.push(";\n");
|
||||
} else {
|
||||
let last = sb[sb.length - 1];
|
||||
if (last.length && (
|
||||
last.charCodeAt(last.length - 1) == CharCode.CLOSEBRACE ||
|
||||
last.charCodeAt(last.length - 1) == CharCode.SEMICOLON)
|
||||
let lastCharPos = last.length - 1;
|
||||
if (lastCharPos >= 0 && (
|
||||
last.charCodeAt(lastCharPos) == CharCode.CLOSEBRACE ||
|
||||
last.charCodeAt(lastCharPos) == CharCode.SEMICOLON)
|
||||
) {
|
||||
sb.push("\n");
|
||||
} else {
|
||||
@ -778,8 +789,12 @@ export class ASTBuilder {
|
||||
this.serializeExternalModifiers(node);
|
||||
var sb = this.sb;
|
||||
if (node.is(CommonFlags.ABSTRACT)) sb.push("abstract ");
|
||||
sb.push("class ");
|
||||
this.visitIdentifierExpression(node.name);
|
||||
if (node.name.text.length) {
|
||||
sb.push("class ");
|
||||
this.visitIdentifierExpression(node.name);
|
||||
} else {
|
||||
sb.push("class");
|
||||
}
|
||||
var typeParameters = node.typeParameters;
|
||||
var numTypeParameters = typeParameters.length;
|
||||
if (numTypeParameters) {
|
||||
|
142
src/parser.ts
142
src/parser.ts
@ -42,6 +42,8 @@ import {
|
||||
Expression,
|
||||
AssertionKind,
|
||||
CallExpression,
|
||||
ClassExpression,
|
||||
FunctionExpression,
|
||||
IdentifierExpression,
|
||||
StringLiteralExpression,
|
||||
|
||||
@ -60,7 +62,6 @@ import {
|
||||
ExportStatement,
|
||||
ExpressionStatement,
|
||||
ForStatement,
|
||||
FunctionExpression,
|
||||
FunctionDeclaration,
|
||||
IfStatement,
|
||||
ImportDeclaration,
|
||||
@ -82,8 +83,7 @@ import {
|
||||
|
||||
mangleInternalPath,
|
||||
nodeIsCallable,
|
||||
nodeIsGenericCallable,
|
||||
InterfaceDeclaration
|
||||
nodeIsGenericCallable
|
||||
} from "./ast";
|
||||
|
||||
const builtinsFile = LIBRARY_PREFIX + "builtins.ts";
|
||||
@ -1544,6 +1544,49 @@ export class Parser extends DiagnosticEmitter {
|
||||
return declaration;
|
||||
}
|
||||
|
||||
parseClassExpression(tn: Tokenizer): ClassExpression | null {
|
||||
|
||||
// at 'class': Identifier? '{' ... '}'
|
||||
|
||||
var startPos = tn.tokenPos;
|
||||
var name: IdentifierExpression;
|
||||
|
||||
if (tn.skipIdentifier()) {
|
||||
name = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
|
||||
} else {
|
||||
name = Node.createEmptyIdentifierExpression(tn.range(tn.pos));
|
||||
}
|
||||
|
||||
if (!tn.skip(Token.OPENBRACE)) {
|
||||
this.error(
|
||||
DiagnosticCode._0_expected,
|
||||
tn.range(tn.pos), "{"
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
var members = new Array<DeclarationStatement>();
|
||||
var declaration = Node.createClassDeclaration(
|
||||
name,
|
||||
[],
|
||||
null,
|
||||
null,
|
||||
members,
|
||||
null,
|
||||
CommonFlags.NONE,
|
||||
tn.range(startPos, tn.pos)
|
||||
);
|
||||
if (!tn.skip(Token.CLOSEBRACE)) {
|
||||
do {
|
||||
let member = this.parseClassMember(tn, declaration);
|
||||
if (!member) return null;
|
||||
member.parent = declaration;
|
||||
members.push(<DeclarationStatement>member);
|
||||
} while (!tn.skip(Token.CLOSEBRACE));
|
||||
}
|
||||
return Node.createClassExpression(declaration);
|
||||
}
|
||||
|
||||
parseClassMember(
|
||||
tn: Tokenizer,
|
||||
parent: ClassDeclaration
|
||||
@ -2877,20 +2920,8 @@ export class Parser extends DiagnosticEmitter {
|
||||
|
||||
var token = tn.next(IdentifierHandling.PREFER);
|
||||
var startPos = tn.tokenPos;
|
||||
var expr: Expression | null = null;
|
||||
|
||||
if (token == Token.NULL) {
|
||||
return Node.createNullExpression(tn.range());
|
||||
}
|
||||
if (token == Token.TRUE) {
|
||||
return Node.createTrueExpression(tn.range());
|
||||
}
|
||||
if (token == Token.FALSE) {
|
||||
return Node.createFalseExpression(tn.range());
|
||||
}
|
||||
|
||||
var precedence = determinePrecedenceStart(token);
|
||||
if (precedence != Precedence.INVALID) {
|
||||
if (precedence != Precedence.NONE) {
|
||||
let operand: Expression | null;
|
||||
|
||||
// TODO: SpreadExpression, YieldExpression (currently become unsupported UnaryPrefixExpressions)
|
||||
@ -2934,8 +2965,13 @@ export class Parser extends DiagnosticEmitter {
|
||||
return Node.createUnaryPrefixExpression(token, operand, tn.range(startPos, tn.pos));
|
||||
}
|
||||
|
||||
var expr: Expression | null = null;
|
||||
switch (token) {
|
||||
|
||||
case Token.NULL: return Node.createNullExpression(tn.range());
|
||||
case Token.TRUE: return Node.createTrueExpression(tn.range());
|
||||
case Token.FALSE: return Node.createFalseExpression(tn.range());
|
||||
|
||||
// ParenthesizedExpression
|
||||
// FunctionExpression
|
||||
case Token.OPENPAREN: {
|
||||
@ -3098,6 +3134,9 @@ export class Parser extends DiagnosticEmitter {
|
||||
case Token.FUNCTION: {
|
||||
return this.parseFunctionExpression(tn);
|
||||
}
|
||||
case Token.CLASS: {
|
||||
return this.parseClassExpression(tn);
|
||||
}
|
||||
default: {
|
||||
this.error(
|
||||
DiagnosticCode.Expression_expected,
|
||||
@ -3163,8 +3202,9 @@ export class Parser extends DiagnosticEmitter {
|
||||
|
||||
parseExpression(
|
||||
tn: Tokenizer,
|
||||
precedence: Precedence = 0
|
||||
precedence: Precedence = Precedence.COMMA
|
||||
): Expression | null {
|
||||
assert(precedence != Precedence.NONE);
|
||||
|
||||
var expr = this.parseExpressionStart(tn);
|
||||
if (!expr) return null;
|
||||
@ -3264,7 +3304,10 @@ export class Parser extends DiagnosticEmitter {
|
||||
);
|
||||
return null;
|
||||
}
|
||||
let ifElse = this.parseExpression(tn, precedence > Precedence.COMMA ? Precedence.COMMA + 1 : 0);
|
||||
let ifElse = this.parseExpression(tn, precedence > Precedence.COMMA
|
||||
? Precedence.COMMA + 1
|
||||
: Precedence.COMMA
|
||||
);
|
||||
if (!ifElse) return null;
|
||||
expr = Node.createTernaryExpression(
|
||||
expr,
|
||||
@ -3372,23 +3415,53 @@ export class Parser extends DiagnosticEmitter {
|
||||
}
|
||||
|
||||
/** Skips over a block on errors in an attempt to reduce unnecessary diagnostic noise. */
|
||||
// skipBlock(tn: Tokenizer): void {
|
||||
// var depth = 0;
|
||||
// var token: Token;
|
||||
// do {
|
||||
// token = tn.next();
|
||||
// if (token == Token.OPENBRACE) {
|
||||
// ++depth;
|
||||
// } else if (token == Token.CLOSEBRACE) {
|
||||
// if (depth) --depth;
|
||||
// if (!depth) break; // done
|
||||
// }
|
||||
// } while (token != Token.ENDOFFILE);
|
||||
// }
|
||||
skipBlock(tn: Tokenizer): void {
|
||||
// at '{': ... '}'
|
||||
var depth = 1;
|
||||
var again = true;
|
||||
do {
|
||||
switch (tn.next()) {
|
||||
case Token.ENDOFFILE: {
|
||||
this.error(
|
||||
DiagnosticCode._0_expected,
|
||||
tn.range(), "}"
|
||||
);
|
||||
again = false;
|
||||
break;
|
||||
}
|
||||
case Token.OPENBRACE: {
|
||||
++depth;
|
||||
break;
|
||||
}
|
||||
case Token.CLOSEBRACE: {
|
||||
--depth;
|
||||
if (!depth) again = false;
|
||||
break;
|
||||
}
|
||||
case Token.IDENTIFIER: {
|
||||
tn.readIdentifier();
|
||||
break;
|
||||
}
|
||||
case Token.STRINGLITERAL: {
|
||||
tn.readString();
|
||||
break;
|
||||
}
|
||||
case Token.INTEGERLITERAL: {
|
||||
tn.readInteger();
|
||||
break;
|
||||
}
|
||||
case Token.FLOATLITERAL: {
|
||||
tn.readFloat();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (again);
|
||||
}
|
||||
}
|
||||
|
||||
/** Operator precedence from least to largest. */
|
||||
export const enum Precedence {
|
||||
NONE,
|
||||
COMMA,
|
||||
SPREAD,
|
||||
YIELD,
|
||||
@ -3409,8 +3482,7 @@ export const enum Precedence {
|
||||
UNARY_POSTFIX,
|
||||
CALL,
|
||||
MEMBERACCESS,
|
||||
GROUPING,
|
||||
INVALID = -1
|
||||
GROUPING
|
||||
}
|
||||
|
||||
/** Determines the precedence of a starting token. */
|
||||
@ -3428,8 +3500,8 @@ function determinePrecedenceStart(kind: Token): Precedence {
|
||||
case Token.VOID:
|
||||
case Token.DELETE: return Precedence.UNARY_PREFIX;
|
||||
case Token.NEW: return Precedence.MEMBERACCESS;
|
||||
default: return Precedence.INVALID;
|
||||
}
|
||||
return Precedence.NONE;
|
||||
}
|
||||
|
||||
/** Determines the precende of a non-starting token. */
|
||||
@ -3480,8 +3552,8 @@ function determinePrecedence(kind: Token): Precedence {
|
||||
case Token.DOT:
|
||||
case Token.NEW:
|
||||
case Token.OPENBRACKET: return Precedence.MEMBERACCESS;
|
||||
default: return Precedence.INVALID;
|
||||
}
|
||||
return Precedence.NONE;
|
||||
}
|
||||
|
||||
/** Determines whether a non-starting token is right associative. */
|
||||
|
5
tests/parser/class-expression.ts
Normal file
5
tests/parser/class-expression.ts
Normal file
@ -0,0 +1,5 @@
|
||||
var a = class {
|
||||
foo(): void {}
|
||||
bar: i32;
|
||||
};
|
||||
var b = class Foo {};
|
5
tests/parser/class-expression.ts.fixture.ts
Normal file
5
tests/parser/class-expression.ts.fixture.ts
Normal file
@ -0,0 +1,5 @@
|
||||
var a = class {
|
||||
foo(): void {}
|
||||
bar: i32;
|
||||
};
|
||||
var b = class Foo {};
|
Reference in New Issue
Block a user