diff --git a/lib/mujs/index.ts b/lib/mujs/index.ts index d372dca13..86feba02c 100644 --- a/lib/mujs/index.ts +++ b/lib/mujs/index.ts @@ -1,5 +1,6 @@ // Copyright 2016 Marapongo, Inc. All rights reserved. import * as lib from "./lib"; -export { lib }; +import * as runtime from "./runtime"; +export { lib, runtime }; diff --git a/lib/mujs/lib/error.ts b/lib/mujs/lib/errors.ts similarity index 85% rename from lib/mujs/lib/error.ts rename to lib/mujs/lib/errors.ts index 4ce766746..b15285734 100644 --- a/lib/mujs/lib/error.ts +++ b/lib/mujs/lib/errors.ts @@ -15,3 +15,10 @@ export class Error { // SyntaxError, TypeError, etc.) Unfortunately, unless we come up with some clever way of mapping MuIL runtime // errors into their ECMAScript equivalents, we aren't going to have perfect compatibility with error path logic. +export class TypeError extends Error { + constructor(message?: string) { + super(message); + this.name = "TypeError"; + } +} + diff --git a/lib/mujs/lib/index.ts b/lib/mujs/lib/index.ts index 02ce89b91..b15d056a4 100644 --- a/lib/mujs/lib/index.ts +++ b/lib/mujs/lib/index.ts @@ -2,5 +2,6 @@ // The lib module exports the standard ECMAScript APIs. -export * from "./error"; +export * from "./errors"; +export * from "./types"; diff --git a/lib/mujs/lib/types.ts b/lib/mujs/lib/types.ts new file mode 100644 index 000000000..ac4ff57d7 --- /dev/null +++ b/lib/mujs/lib/types.ts @@ -0,0 +1,23 @@ +// Copyright 2016 Marapongo, Inc. All rights reserved. + +export class Boolean { + public data: boolean; + constructor(data: boolean) { + this.data = data; + } +} + +export class String { + public data: string; + constructor(data: string) { + this.data = data; + } +} + +export class Number { + public data: number; + constructor(data: number) { + this.data = data; + } +} + diff --git a/lib/mujs/runtime/assert.ts b/lib/mujs/runtime/assert.ts new file mode 100644 index 000000000..ae9b7c6ac --- /dev/null +++ b/lib/mujs/runtime/assert.ts @@ -0,0 +1,10 @@ +// Copyright 2016 Marapongo, Inc. All rights reserved. + +import {Error} from "../lib"; + +export function assert(b: boolean): void { + if (!b) { + throw new Error("An assertion has failed"); + } +} + diff --git a/lib/mujs/runtime/ecmascript.ts b/lib/mujs/runtime/ecmascript.ts new file mode 100644 index 000000000..fff1e3399 --- /dev/null +++ b/lib/mujs/runtime/ecmascript.ts @@ -0,0 +1,216 @@ +// Copyright 2016 Marapongo, Inc. All rights reserved. + +import * as mu from "@mu/mu"; +import {assert} from "./assert"; +import {Boolean, Number, String, TypeError} from "../lib"; + +// The abstract operation ToString converts argument to a value of type String according to the table in +// https://tc39.github.io/ecma262/#sec-tostring. +export function toString(argument: Object): string { + if (argument === undefined) { + return "undefined"; + } + if (argument === null) { + return "null"; + } + if (isBoolean(argument)) { + if (argument === true) { + return "true"; + } + return "false"; + } + if (isNumber(argument)) { + } + if (isSymbol(argument)) { + throw new TypeError(); + } + if (isString(argument)) { + return argument; + } + + // For other objects, convert to a primitive value and stringify that. + let primValue: Object = toPrimitive(argument, "string"); + return toString(primValue); +} + +export function isObject(input: Object): boolean { + return !isPrimitive(input); +} + +export function isPrimitive(input: Object): boolean { + return input === undefined || input === null || + isBoolean(input) || isNumber(input) || isString(input) || isSymbol(input) +} + +export function isBoolean(input: Object): boolean { + return (typeof(input) === "bool"); +} + +export function isNumber(input: Object): boolean { + return (typeof(input) === "number"); +} + +export function isString(input: Object): boolean { + return (typeof(input) === "string"); +} + +export function isSymbol(input: Object): boolean { + // TODO: implement symbols. + return false; +} + +// The abstract operation toPrimitive takes an input argument and an optional argument preferredType. The abstract +// operation toPrimitive converts its input argument to a non-Object type. If an object is capable of converting to +// more than one primitive type, it may use the optional hint preferredType to favor that type. Conversion occurs +// according to the table in https://tc39.github.io/ecma262/#sec-toprimitive. +export function toPrimitive(input: Object, preferredType: string): Object { + if (isPrimitive(input)) { + return input; + } + + let hint: string; + if (preferredType) { + hint = preferredType; + } + else { + hint = "default"; + } + + let exoticToPrim: Object | undefined = getMethod(input, "@@toPrimitive"); + if (exoticToPrim) { + let result: Object = call(exoticToPrim, input, [ hint ]); + if (isObject(result)) { + throw new TypeError(""); + } + return result; + } + + if (hint === "default") { + hint = "number"; + } + return ordinaryToPrimitive(input, hint); +} + +// When the abstract operation OrdinaryToPrimitive is called with arguments O and hint, the steps outlined in +// https://tc39.github.io/ecma262/#sec-ordinarytoprimitive are taken. +export function ordinaryToPrimitive(o: Object, hint: string): Object { + assert(isObject(o)); + + let methodNames: string[]; + switch (hint) { + case "string": + methodNames = [ "toString", "valueOf" ]; + break; + case "number": + methodNames = [ "valueOf", "toString" ]; + break; + default: + assert(false); + methodNames = []; + } + + for (let name of methodNames) { + let method: Object = get(o, name); + if (isCallable(method)) { + let result: Object = call(method, o); + if (isPrimitive(result)) { + return result; + } + } + } + + throw new TypeError(); +} + +// The abstract operation ToObject converts argument to a value of type Object according to the table in +// https://tc39.github.io/ecma262/#sec-toobject. +export function toObject(argument: Object): Object { + if (argument === undefined || argument === null) { + throw new TypeError(); + } + if (isBoolean(argument)) { + return new Boolean(argument); + } + if (isNumber(argument)) { + return new Number(argument); + } + if (isString(argument)) { + return new String(argument); + } + if (isSymbol(argument)) { + // TODO: implement symbols. + } + return argument; +} + +// The abstract operation Get is used to retrieve the value of a specific property of an object. The operation is called +// with arguments O and P where O is the object and P is the property key. This abstract operation performs the steps +// outlined in https://tc39.github.io/ecma262/#sec-get-o-p. +export function get(o: Object, p: string): Object { + assert(isObject(o)); + assert(isPropertyKey(p)); + return (o)[p]; +} + +// The abstract operation GetV is used to retrieve the value of a specific property of an ECMAScript language value. If +// the value is not an object, the property lookup is performed using a wrapper object appropriate for the type of the +// value. The operation is called with arguments V and P where V is the value and P is the property key. This abstract +// operation performs the steps outlined in https://tc39.github.io/ecma262/#sec-getv. +export function getV(v: Object, p: Object): Object { + assert(isPropertyKey(p)); + let o: Object = toObject(v); + return (o)[p]; +} + +// The abstract operation GetMethod is used to get the value of a specific property of an ECMAScript language value when +// the value of the property is expected to be a function. The operation is called with arguments V and P where V is the +// ECMAScript language value, P is the property key. This abstract operation performs the steps outlined in +// https://tc39.github.io/ecma262/#sec-getmethod. +export function getMethod(v: Object, p: Object): Object | undefined { + assert(isPropertyKey(p)); + let func: Object = getV(v, p); + if (func === undefined || func === null) { + return undefined; + } + if (!isCallable(func)) { + throw new TypeError(); + } + return func; +} + +// The abstract operation IsPropertyKey determines if argument, which must be an ECMAScript language value, is a value +// that may be used as a property key, as described in https://tc39.github.io/ecma262/#sec-ispropertykey. +export function isPropertyKey(argument: Object): boolean { + if (isString(argument)) { + return true; + } + if (isSymbol(argument)) { + return true; + } + return false; +} + +// The abstract operation Call is used to call the [[Call]] internal method of a function object. The operation is +// called with arguments F, V, and optionally argumentsList where F is the function object, V is an ECMAScript language +// value that is the this value of the [[Call]], and argumentsList is the value passed to the corresponding argument of +// the internal method. If argumentsList is not present, a new empty List is used as its value. This abstract operation +// performs the steps outlined in https://tc39.github.io/ecma262/#sec-call. +export function call(f: Object, v: Object, argumentsList?: Object[]): Object { + if (!isCallable(f)) { + throw new TypeError(); + } + if (!argumentsList) { + argumentsList = []; + } + return mu.runtime.dynamicInvoke(f, v, argumentsList); +} + +// The abstract operation IsCallable determines if argument, which must be an ECMAScript language value, is a callable +// function with a [[Call]] internal method, as per https://tc39.github.io/ecma262/#sec-iscallable. +export function isCallable(argument: Object): boolean { + if (!isObject(argument)) { + return false; + } + return mu.runtime.isFunction(argument); +} + diff --git a/lib/mujs/runtime/index.ts b/lib/mujs/runtime/index.ts new file mode 100644 index 000000000..152b8535f --- /dev/null +++ b/lib/mujs/runtime/index.ts @@ -0,0 +1,7 @@ +// Copyright 2016 Marapongo, Inc. All rights reserved. + +// The runtime module contains helpers that the MuJS compiler emits to do certain things. + +export * from "./assert"; +export * from "./ecmascript"; + diff --git a/lib/mujs/tsconfig.json b/lib/mujs/tsconfig.json index 5aeaf0008..9d48666c8 100644 --- a/lib/mujs/tsconfig.json +++ b/lib/mujs/tsconfig.json @@ -19,7 +19,10 @@ "index.ts", "lib/index.ts", - "lib/error.ts" + "lib/errors.ts", + "lib/types.ts", + + "runtime/ecmascript" ] }