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.
This commit is contained in:
dcodeIO
2018-07-14 04:00:04 +02:00
parent 72cb1e9008
commit 9e508de69a
15 changed files with 946 additions and 12 deletions

View File

@ -432,6 +432,18 @@ export abstract class Node {
return expr;
}
static createObjectLiteralExpression(
names: IdentifierExpression[],
values: Expression[],
range: Range
): ObjectLiteralExpression {
var expr = new ObjectLiteralExpression();
expr.range = range;
expr.names = names;
expr.values = values;
return expr;
}
static createParenthesizedExpression(
expression: Expression,
range: Range
@ -1363,6 +1375,16 @@ export class NullExpression extends IdentifierExpression {
text = "null";
}
/** Represents an object literal expression. */
export class ObjectLiteralExpression extends LiteralExpression {
literalKind = LiteralKind.OBJECT;
/** Field names. */
names: IdentifierExpression[];
/** Field values. */
values: Expression[];
}
/** Represents a parenthesized expression. */
export class ParenthesizedExpression extends Expression {
kind = NodeKind.PARENTHESIZED;

View File

@ -71,7 +71,11 @@ export enum CommonFlags {
/** Is a virtual method. */
VIRTUAL = 1 << 26,
/** Is the main function. */
MAIN = 1 << 27
MAIN = 1 << 27,
// Other
QUOTED = 1 << 28
}
/** Path delimiter inserted between file system levels. */

View File

@ -130,6 +130,7 @@ import {
LiteralExpression,
LiteralKind,
NewExpression,
ObjectLiteralExpression,
ParenthesizedExpression,
PropertyAccessExpression,
TernaryExpression,
@ -6144,7 +6145,10 @@ export class Compiler extends DiagnosticEmitter {
assert(!implicitNegate);
return this.compileStaticString((<StringLiteralExpression>expression).value);
}
// case LiteralKind.OBJECT:
case LiteralKind.OBJECT: {
assert(!implicitNegate);
return this.compileObjectLiteral(<ObjectLiteralExpression>expression, contextualType);
}
// case LiteralKind.REGEXP:
}
this.error(
@ -6392,6 +6396,88 @@ export class Compiler extends DiagnosticEmitter {
}
}
compileObjectLiteral(expression: ObjectLiteralExpression, contextualType: Type): ExpressionRef {
var module = this.module;
// contextual type must be a class
var classReference = contextualType.classReference;
if (!classReference || classReference.is(CommonFlags.ABSTRACT)) {
this.error(
DiagnosticCode.Type_0_is_not_assignable_to_type_1,
expression.range, "<object>", contextualType.toString()
);
return module.createUnreachable();
}
// if present, check that the constructor is compatible with object literals
var ctor = classReference.constructorInstance;
if (ctor) {
if (ctor.signature.requiredParameters) {
this.error(
DiagnosticCode.Constructor_of_class_0_must_not_require_any_arguments,
expression.range, classReference.toString()
);
return module.createUnreachable();
}
if (ctor.is(CommonFlags.PRIVATE)) {
this.error(
DiagnosticCode.Constructor_of_class_0_is_private_and_only_accessible_within_the_class_declaration,
expression.range, classReference.toString()
);
return module.createUnreachable();
}
if (ctor.is(CommonFlags.PROTECTED)) {
this.error(
DiagnosticCode.Constructor_of_class_0_is_protected_and_only_accessible_within_the_class_declaration,
expression.range, classReference.toString()
);
return module.createUnreachable();
}
}
// check and compile field values
var names = expression.names;
var numNames = names.length;
var values = expression.values;
var members = classReference.members;
var hasErrors = false;
var exprs = new Array<ExpressionRef>(numNames + 2);
var tempLocal = this.currentFunction.getTempLocal(this.options.usizeType);
assert(numNames == values.length);
for (let i = 0, k = numNames; i < k; ++i) {
let member = members ? members.get(names[i].text) : null;
if (!member || member.kind != ElementKind.FIELD) {
this.error(
DiagnosticCode.Property_0_does_not_exist_on_type_1,
names[i].range, names[i].text, classReference.toString()
);
hasErrors = true;
continue;
}
let type = (<Field>member).type;
exprs[i + 1] = this.module.createStore( // TODO: handle setters as well
type.byteSize,
this.module.createGetLocal(tempLocal.index, this.options.nativeSizeType),
this.compileExpression(values[i], (<Field>member).type, ConversionKind.IMPLICIT, WrapMode.NONE),
type.toNativeType(),
(<Field>member).memoryOffset
);
}
this.currentType = classReference.type.nonNullableType;
if (hasErrors) return module.createUnreachable();
// allocate a new instance first and assign 'this' to the temp. local
exprs[0] = module.createSetLocal(
tempLocal.index,
compileBuiltinAllocate(this, classReference, expression)
);
// once all field values have been set, return 'this'
exprs[exprs.length - 1] = module.createGetLocal(tempLocal.index, this.options.nativeSizeType);
return module.createBlock(null, exprs, this.options.nativeSizeType);
}
compileNewExpression(expression: NewExpression, contextualType: Type): ExpressionRef {
var module = this.module;
var options = this.options;

View File

@ -25,6 +25,7 @@ export enum DiagnosticCode {
Duplicate_decorator = 213,
An_allocator_must_be_declared_to_allocate_memory_Try_importing_allocator_arena_or_allocator_tlsf = 214,
Optional_parameter_must_have_an_initializer = 215,
Constructor_of_class_0_must_not_require_any_arguments = 216,
Unterminated_string_literal = 1002,
Identifier_expected = 1003,
_0_expected = 1005,
@ -111,6 +112,8 @@ export enum DiagnosticCode {
Expected_at_least_0_arguments_but_got_1 = 2555,
Expected_0_type_arguments_but_got_1 = 2558,
A_member_initializer_in_a_enum_declaration_cannot_reference_members_declared_after_it_including_members_defined_in_other_enums = 2651,
Constructor_of_class_0_is_private_and_only_accessible_within_the_class_declaration = 2673,
Constructor_of_class_0_is_protected_and_only_accessible_within_the_class_declaration = 2674,
Namespace_0_has_no_exported_member_1 = 2694,
File_0_not_found = 6054,
Numeric_separators_are_not_allowed_here = 6188,
@ -138,6 +141,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
case 213: return "Duplicate decorator.";
case 214: return "An allocator must be declared to allocate memory. Try importing allocator/arena or allocator/tlsf.";
case 215: return "Optional parameter must have an initializer.";
case 216: return "Constructor of class '{0}' must not require any arguments.";
case 1002: return "Unterminated string literal.";
case 1003: return "Identifier expected.";
case 1005: return "'{0}' expected.";
@ -224,6 +228,8 @@ export function diagnosticCodeToString(code: DiagnosticCode): string {
case 2555: return "Expected at least {0} arguments, but got {1}.";
case 2558: return "Expected {0} type arguments, but got {1}.";
case 2651: return "A member initializer in a enum declaration cannot reference members declared after it, including members defined in other enums.";
case 2673: return "Constructor of class '{0}' is private and only accessible within the class declaration.";
case 2674: return "Constructor of class '{0}' is protected and only accessible within the class declaration.";
case 2694: return "Namespace '{0}' has no exported member '{1}'.";
case 6054: return "File '{0}' not found.";
case 6188: return "Numeric separators are not allowed here.";

View File

@ -17,6 +17,7 @@
"Duplicate decorator.": 213,
"An allocator must be declared to allocate memory. Try importing allocator/arena or allocator/tlsf.": 214,
"Optional parameter must have an initializer.": 215,
"Constructor of class '{0}' must not require any arguments.": 216,
"Unterminated string literal.": 1002,
"Identifier expected.": 1003,
@ -105,6 +106,8 @@
"Expected at least {0} arguments, but got {1}.": 2555,
"Expected {0} type arguments, but got {1}.": 2558,
"A member initializer in a enum declaration cannot reference members declared after it, including members defined in other enums.": 2651,
"Constructor of class '{0}' is private and only accessible within the class declaration.": 2673,
"Constructor of class '{0}' is protected and only accessible within the class declaration.": 2674,
"Namespace '{0}' has no exported member '{1}'.": 2694,
"File '{0}' not found.": 6054,

View File

@ -39,6 +39,8 @@ import {
UnaryPostfixExpression,
UnaryExpression,
UnaryPrefixExpression,
ClassExpression,
ObjectLiteralExpression,
Statement,
BlockStatement,
@ -76,8 +78,7 @@ import {
ParameterNode,
ParameterKind,
ExportMember,
SwitchCase,
ClassExpression
SwitchCase
} from "../ast";
import {
@ -411,7 +412,8 @@ export class ASTBuilder {
// expressions
visitIdentifierExpression(node: IdentifierExpression): void {
this.sb.push(node.text);
if (node.is(CommonFlags.QUOTED)) this.visitStringLiteral(node.text);
else this.sb.push(node.text);
}
visitArrayLiteralExpression(node: ArrayLiteralExpression): void {
@ -429,6 +431,33 @@ export class ASTBuilder {
sb.push("]");
}
visitObjectLiteralExpression(node: ObjectLiteralExpression): void {
var sb = this.sb;
var names = node.names;
var values = node.values;
var numElements = names.length;
assert(numElements == values.length);
if (numElements) {
sb.push("{\n");
indent(sb, ++this.indentLevel);
this.visitNode(names[0]);
sb.push(": ");
this.visitNode(values[0]);
for (let i = 1; i < numElements; ++i) {
sb.push(",\n");
indent(sb, this.indentLevel);
this.visitNode(names[i]);
sb.push(": ");
this.visitNode(values[i]);
}
sb.push("\n");
indent(sb, --this.indentLevel);
sb.push("}");
} else {
sb.push("{}");
}
}
visitAssertionExpression(node: AssertionExpression): void {
var sb = this.sb;
if (node.assertionKind == AssertionKind.PREFIX) {
@ -542,10 +571,10 @@ export class ASTBuilder {
this.visitArrayLiteralExpression(<ArrayLiteralExpression>node);
break;
}
// case LiteralKind.OBJECT: {
// this.serializeObjectLiteralExpression(<ObjectLiteralExpression>node);
// break;
// }
case LiteralKind.OBJECT: {
this.visitObjectLiteralExpression(<ObjectLiteralExpression>node);
break;
}
default: {
assert(false);
break;

View File

@ -3073,6 +3073,51 @@ export class Parser extends DiagnosticEmitter {
}
return Node.createArrayLiteralExpression(elementExpressions, tn.range(startPos, tn.pos));
}
// ObjectLiteralExpression
case Token.OPENBRACE: {
let startPos = tn.tokenPos;
let names = new Array<IdentifierExpression>();
let values = new Array<Expression>();
let name: IdentifierExpression;
while (!tn.skip(Token.CLOSEBRACE)) {
if (!tn.skipIdentifier()) {
if (!tn.skip(Token.STRINGLITERAL)) {
this.error(
DiagnosticCode.Identifier_expected,
tn.range(),
);
return null;
}
name = Node.createIdentifierExpression(tn.readString(), tn.range());
name.set(CommonFlags.QUOTED);
} else {
name = Node.createIdentifierExpression(tn.readIdentifier(), tn.range());
}
if (!tn.skip(Token.COLON)) {
this.error(
DiagnosticCode._0_expected,
tn.range(), ":"
);
return null;
}
let value = this.parseExpression(tn, Precedence.COMMA + 1);
if (!value) return null;
names.push(name);
values.push(value);
if (!tn.skip(Token.COMMA)) {
if (tn.skip(Token.CLOSEBRACE)) {
break;
} else {
this.error(
DiagnosticCode._0_expected,
tn.range(), "}"
);
return null;
}
}
}
return Node.createObjectLiteralExpression(names, values, tn.range(startPos, tn.pos));
}
// AssertionExpression (unary prefix)
case Token.LESSTHAN: {
let toType = this.parseType(tn);