Skip to content

Commit 9e508de

Browse files
committed
Implement object literal parsing; Instantiate classes from object literals
Essentially, if the contextual type is a class with a constructor that takes zero arguments or doesn't have a constructor at all, an object literal can be used to initialize a new instance of that class with preset values.
1 parent 72cb1e9 commit 9e508de

15 files changed

+946
-12
lines changed

.gitattributes

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
bin/* text eol=lf
2-
dist/* -diff
2+
dist/* binary
33
scripts/*.sh eol=lf

dist/assemblyscript.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/assemblyscript.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/ast.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,18 @@ export abstract class Node {
432432
return expr;
433433
}
434434

435+
static createObjectLiteralExpression(
436+
names: IdentifierExpression[],
437+
values: Expression[],
438+
range: Range
439+
): ObjectLiteralExpression {
440+
var expr = new ObjectLiteralExpression();
441+
expr.range = range;
442+
expr.names = names;
443+
expr.values = values;
444+
return expr;
445+
}
446+
435447
static createParenthesizedExpression(
436448
expression: Expression,
437449
range: Range
@@ -1363,6 +1375,16 @@ export class NullExpression extends IdentifierExpression {
13631375
text = "null";
13641376
}
13651377

1378+
/** Represents an object literal expression. */
1379+
export class ObjectLiteralExpression extends LiteralExpression {
1380+
literalKind = LiteralKind.OBJECT;
1381+
1382+
/** Field names. */
1383+
names: IdentifierExpression[];
1384+
/** Field values. */
1385+
values: Expression[];
1386+
}
1387+
13661388
/** Represents a parenthesized expression. */
13671389
export class ParenthesizedExpression extends Expression {
13681390
kind = NodeKind.PARENTHESIZED;

src/common.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,11 @@ export enum CommonFlags {
7171
/** Is a virtual method. */
7272
VIRTUAL = 1 << 26,
7373
/** Is the main function. */
74-
MAIN = 1 << 27
74+
MAIN = 1 << 27,
75+
76+
// Other
77+
78+
QUOTED = 1 << 28
7579
}
7680

7781
/** Path delimiter inserted between file system levels. */

src/compiler.ts

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ import {
130130
LiteralExpression,
131131
LiteralKind,
132132
NewExpression,
133+
ObjectLiteralExpression,
133134
ParenthesizedExpression,
134135
PropertyAccessExpression,
135136
TernaryExpression,
@@ -6144,7 +6145,10 @@ export class Compiler extends DiagnosticEmitter {
61446145
assert(!implicitNegate);
61456146
return this.compileStaticString((<StringLiteralExpression>expression).value);
61466147
}
6147-
// case LiteralKind.OBJECT:
6148+
case LiteralKind.OBJECT: {
6149+
assert(!implicitNegate);
6150+
return this.compileObjectLiteral(<ObjectLiteralExpression>expression, contextualType);
6151+
}
61486152
// case LiteralKind.REGEXP:
61496153
}
61506154
this.error(
@@ -6392,6 +6396,88 @@ export class Compiler extends DiagnosticEmitter {
63926396
}
63936397
}
63946398

6399+
compileObjectLiteral(expression: ObjectLiteralExpression, contextualType: Type): ExpressionRef {
6400+
var module = this.module;
6401+
6402+
// contextual type must be a class
6403+
var classReference = contextualType.classReference;
6404+
if (!classReference || classReference.is(CommonFlags.ABSTRACT)) {
6405+
this.error(
6406+
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
6407+
expression.range, "<object>", contextualType.toString()
6408+
);
6409+
return module.createUnreachable();
6410+
}
6411+
6412+
// if present, check that the constructor is compatible with object literals
6413+
var ctor = classReference.constructorInstance;
6414+
if (ctor) {
6415+
if (ctor.signature.requiredParameters) {
6416+
this.error(
6417+
DiagnosticCode.Constructor_of_class_0_must_not_require_any_arguments,
6418+
expression.range, classReference.toString()
6419+
);
6420+
return module.createUnreachable();
6421+
}
6422+
if (ctor.is(CommonFlags.PRIVATE)) {
6423+
this.error(
6424+
DiagnosticCode.Constructor_of_class_0_is_private_and_only_accessible_within_the_class_declaration,
6425+
expression.range, classReference.toString()
6426+
);
6427+
return module.createUnreachable();
6428+
}
6429+
if (ctor.is(CommonFlags.PROTECTED)) {
6430+
this.error(
6431+
DiagnosticCode.Constructor_of_class_0_is_protected_and_only_accessible_within_the_class_declaration,
6432+
expression.range, classReference.toString()
6433+
);
6434+
return module.createUnreachable();
6435+
}
6436+
}
6437+
6438+
// check and compile field values
6439+
var names = expression.names;
6440+
var numNames = names.length;
6441+
var values = expression.values;
6442+
var members = classReference.members;
6443+
var hasErrors = false;
6444+
var exprs = new Array<ExpressionRef>(numNames + 2);
6445+
var tempLocal = this.currentFunction.getTempLocal(this.options.usizeType);
6446+
assert(numNames == values.length);
6447+
for (let i = 0, k = numNames; i < k; ++i) {
6448+
let member = members ? members.get(names[i].text) : null;
6449+
if (!member || member.kind != ElementKind.FIELD) {
6450+
this.error(
6451+
DiagnosticCode.Property_0_does_not_exist_on_type_1,
6452+
names[i].range, names[i].text, classReference.toString()
6453+
);
6454+
hasErrors = true;
6455+
continue;
6456+
}
6457+
let type = (<Field>member).type;
6458+
exprs[i + 1] = this.module.createStore( // TODO: handle setters as well
6459+
type.byteSize,
6460+
this.module.createGetLocal(tempLocal.index, this.options.nativeSizeType),
6461+
this.compileExpression(values[i], (<Field>member).type, ConversionKind.IMPLICIT, WrapMode.NONE),
6462+
type.toNativeType(),
6463+
(<Field>member).memoryOffset
6464+
);
6465+
}
6466+
this.currentType = classReference.type.nonNullableType;
6467+
if (hasErrors) return module.createUnreachable();
6468+
6469+
// allocate a new instance first and assign 'this' to the temp. local
6470+
exprs[0] = module.createSetLocal(
6471+
tempLocal.index,
6472+
compileBuiltinAllocate(this, classReference, expression)
6473+
);
6474+
6475+
// once all field values have been set, return 'this'
6476+
exprs[exprs.length - 1] = module.createGetLocal(tempLocal.index, this.options.nativeSizeType);
6477+
6478+
return module.createBlock(null, exprs, this.options.nativeSizeType);
6479+
}
6480+
63956481
compileNewExpression(expression: NewExpression, contextualType: Type): ExpressionRef {
63966482
var module = this.module;
63976483
var options = this.options;

src/diagnosticMessages.generated.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export enum DiagnosticCode {
2525
Duplicate_decorator = 213,
2626
An_allocator_must_be_declared_to_allocate_memory_Try_importing_allocator_arena_or_allocator_tlsf = 214,
2727
Optional_parameter_must_have_an_initializer = 215,
28+
Constructor_of_class_0_must_not_require_any_arguments = 216,
2829
Unterminated_string_literal = 1002,
2930
Identifier_expected = 1003,
3031
_0_expected = 1005,
@@ -111,6 +112,8 @@ export enum DiagnosticCode {
111112
Expected_at_least_0_arguments_but_got_1 = 2555,
112113
Expected_0_type_arguments_but_got_1 = 2558,
113114
A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums = 2651,
115+
Constructor_of_class_0_is_private_and_only_accessible_within_the_class_declaration = 2673,
116+
Constructor_of_class_0_is_protected_and_only_accessible_within_the_class_declaration = 2674,
114117
Namespace_0_has_no_exported_member_1 = 2694,
115118
File_0_not_found = 6054,
116119
Numeric_separators_are_not_allowed_here = 6188,
@@ -138,6 +141,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
138141
case 213: return "Duplicate decorator.";
139142
case 214: return "An allocator must be declared to allocate memory. Try importing allocator/arena or allocator/tlsf.";
140143
case 215: return "Optional parameter must have an initializer.";
144+
case 216: return "Constructor of class '{0}' must not require any arguments.";
141145
case 1002: return "Unterminated string literal.";
142146
case 1003: return "Identifier expected.";
143147
case 1005: return "'{0}' expected.";
@@ -224,6 +228,8 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
224228
case 2555: return "Expected at least {0} arguments, but got {1}.";
225229
case 2558: return "Expected {0} type arguments, but got {1}.";
226230
case 2651: return "A member initializer in a enum declaration cannot reference members declared after it, including members defined in other enums.";
231+
case 2673: return "Constructor of class '{0}' is private and only accessible within the class declaration.";
232+
case 2674: return "Constructor of class '{0}' is protected and only accessible within the class declaration.";
227233
case 2694: return "Namespace '{0}' has no exported member '{1}'.";
228234
case 6054: return "File '{0}' not found.";
229235
case 6188: return "Numeric separators are not allowed here.";

src/diagnosticMessages.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"Duplicate decorator.": 213,
1818
"An allocator must be declared to allocate memory. Try importing allocator/arena or allocator/tlsf.": 214,
1919
"Optional parameter must have an initializer.": 215,
20+
"Constructor of class '{0}' must not require any arguments.": 216,
2021

2122
"Unterminated string literal.": 1002,
2223
"Identifier expected.": 1003,
@@ -105,6 +106,8 @@
105106
"Expected at least {0} arguments, but got {1}.": 2555,
106107
"Expected {0} type arguments, but got {1}.": 2558,
107108
"A member initializer in a enum declaration cannot reference members declared after it, including members defined in other enums.": 2651,
109+
"Constructor of class '{0}' is private and only accessible within the class declaration.": 2673,
110+
"Constructor of class '{0}' is protected and only accessible within the class declaration.": 2674,
108111
"Namespace '{0}' has no exported member '{1}'.": 2694,
109112

110113
"File '{0}' not found.": 6054,

src/extra/ast.ts

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ import {
3939
UnaryPostfixExpression,
4040
UnaryExpression,
4141
UnaryPrefixExpression,
42+
ClassExpression,
43+
ObjectLiteralExpression,
4244

4345
Statement,
4446
BlockStatement,
@@ -76,8 +78,7 @@ import {
7678
ParameterNode,
7779
ParameterKind,
7880
ExportMember,
79-
SwitchCase,
80-
ClassExpression
81+
SwitchCase
8182
} from "../ast";
8283

8384
import {
@@ -411,7 +412,8 @@ export class ASTBuilder {
411412
// expressions
412413

413414
visitIdentifierExpression(node: IdentifierExpression): void {
414-
this.sb.push(node.text);
415+
if (node.is(CommonFlags.QUOTED)) this.visitStringLiteral(node.text);
416+
else this.sb.push(node.text);
415417
}
416418

417419
visitArrayLiteralExpression(node: ArrayLiteralExpression): void {
@@ -429,6 +431,33 @@ export class ASTBuilder {
429431
sb.push("]");
430432
}
431433

434+
visitObjectLiteralExpression(node: ObjectLiteralExpression): void {
435+
var sb = this.sb;
436+
var names = node.names;
437+
var values = node.values;
438+
var numElements = names.length;
439+
assert(numElements == values.length);
440+
if (numElements) {
441+
sb.push("{\n");
442+
indent(sb, ++this.indentLevel);
443+
this.visitNode(names[0]);
444+
sb.push(": ");
445+
this.visitNode(values[0]);
446+
for (let i = 1; i < numElements; ++i) {
447+
sb.push(",\n");
448+
indent(sb, this.indentLevel);
449+
this.visitNode(names[i]);
450+
sb.push(": ");
451+
this.visitNode(values[i]);
452+
}
453+
sb.push("\n");
454+
indent(sb, --this.indentLevel);
455+
sb.push("}");
456+
} else {
457+
sb.push("{}");
458+
}
459+
}
460+
432461
visitAssertionExpression(node: AssertionExpression): void {
433462
var sb = this.sb;
434463
if (node.assertionKind == AssertionKind.PREFIX) {
@@ -542,10 +571,10 @@ export class ASTBuilder {
542571
this.visitArrayLiteralExpression(<ArrayLiteralExpression>node);
543572
break;
544573
}
545-
// case LiteralKind.OBJECT: {
546-
// this.serializeObjectLiteralExpression(<ObjectLiteralExpression>node);
547-
// break;
548-
// }
574+
case LiteralKind.OBJECT: {
575+
this.visitObjectLiteralExpression(<ObjectLiteralExpression>node);
576+
break;
577+
}
549578
default: {
550579
assert(false);
551580
break;

src/parser.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3073,6 +3073,51 @@ export class Parser extends DiagnosticEmitter {
30733073
}
30743074
return Node.createArrayLiteralExpression(elementExpressions, tn.range(startPos, tn.pos));
30753075
}
3076+
// ObjectLiteralExpression
3077+
case Token.OPENBRACE: {
3078+
let startPos = tn.tokenPos;
3079+
let names = new Array<IdentifierExpression>();
3080+
let values = new Array<Expression>();
3081+
let name: IdentifierExpression;
3082+
while (!tn.skip(Token.CLOSEBRACE)) {
3083+
if (!tn.skipIdentifier()) {
3084+
if (!tn.skip(Token.STRINGLITERAL)) {
3085+
this.error(
3086+
DiagnosticCode.Identifier_expected,
3087+
tn.range(),
3088+
);
3089+
return null;
3090+
}
3091+
name = Node.createIdentifierExpression(tn.readString(), tn.range());
3092+
name.set(CommonFlags.QUOTED);
3093+
} else {
3094+
name = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
3095+
}
3096+
if (!tn.skip(Token.COLON)) {
3097+
this.error(
3098+
DiagnosticCode._0_expected,
3099+
tn.range(), ":"
3100+
);
3101+
return null;
3102+
}
3103+
let value = this.parseExpression(tn, Precedence.COMMA + 1);
3104+
if (!value) return null;
3105+
names.push(name);
3106+
values.push(value);
3107+
if (!tn.skip(Token.COMMA)) {
3108+
if (tn.skip(Token.CLOSEBRACE)) {
3109+
break;
3110+
} else {
3111+
this.error(
3112+
DiagnosticCode._0_expected,
3113+
tn.range(), "}"
3114+
);
3115+
return null;
3116+
}
3117+
}
3118+
}
3119+
return Node.createObjectLiteralExpression(names, values, tn.range(startPos, tn.pos));
3120+
}
30763121
// AssertionExpression (unary prefix)
30773122
case Token.LESSTHAN: {
30783123
let toType = this.parseType(tn);

0 commit comments

Comments
 (0)