futurescript
Version:
A functional style, but highly readable language that compiles to JavaScript.
400 lines (363 loc) • 11.9 kB
JavaScript
import * as $tools from "./tools.mjs";
import * as $print from "./print.mjs";
import * as $node from "./node.mjs";
import * as $lex from "./lex.mjs";
import * as $pattern from "./pattern.mjs";
import {JsBuilder as J} from "./js-builder.mjs";
export class Node {
constructor(obj) {
if (obj !== undefined && obj !== null) {
Object.keys(obj).forEach(key => {
this[key] = obj[key];
});
}
}
setParent(node) {
this._parent = node;
}
// Takes 0, 1, or 2 arguments.
// If no argument, then returns the parent.
// If 1 argument, it can be a type, or a selector function. Returns the ancestor matching it.
// If 2 arguments, and the second argument is `true`, it will return all ancestors from
// this node (excluding) through the matching ancestor (including) in an array. Setting the
// second argument to false is equal to the 1-argument syntax.
// TODO: The functionality doesn't fully match the method name. Need refactor.
getParent() {
let arg = arguments[0];
let returnsArray = arguments[1] === true ? true : false;
if (arg === undefined) {
return this._parent;
}
else if (typeof arg === "function") {
let next = this._parent;
let stack = [next];
while (next !== null && !($tools.classIsClass(arg, Node) ? next instanceof arg : arg(next))) {
next = next._parent;
stack.push(next);
}
return returnsArray ? stack : next;
}
else {
throw new Error("Argument invalid.");
}
}
getRoot() {
let next = this;
while (next._parent !== null) {
next = next._parent;
}
return next;
}
toString(level = 0) {
return $print.printObject(this, this.constructor, level);
}
}
export class MappableNode extends Node {
setLexPart(lexPart) {
this._lexPart = lexPart;
return this;
}
getLexPart() {
return this._lexPart;
}
}
// Piece is not an expression, statement or block. It's a simple node that
// the source and the compiled code look the same (unless renamed to avoid conflict).
export class Piece extends MappableNode {
constructor() {
super();
let arg = arguments[0];
if (typeof arg === "string") {
this.value = arg;
}
else if (arg instanceof $lex.LexPart) {
if (arg.token() instanceof $lex.InlineNormalString) {
this.value = "\"" + arg.lex.at(arg.startIndex + 2).value + "\"";
}
else {
this.value = arg.token().value;
}
this.setLexPart(arg);
}
else if (arg instanceof Piece) {
this.value = arg.value;
this.setLexPart(arg.getLexPart());
}
}
compile() {
return new J(this.value).shareLexPart(this);
}
toString() {
return $print.printAtom(this);
}
}
export class Arr extends Node {
constructor(value) {
super();
this.value = value;
}
toString(level = 0) {
return $print.printArray(this.value, this.constructor, level);
}
}
export class Xy extends Node {
constructor(x, y) {
super();
this.x = x;
this.y = y;
}
}
export class LocalVariable extends Piece {}
export class VariableAssignee extends Node {
// `obj` has 4 properties: variable, export, ifvoid, ifnull. Only `variable` is mandatory,
// others are all optional defaulting to `false`.
constructor(obj) {
if (obj.export === undefined) {
obj.export = false;
}
if (obj.ifvoid === undefined) {
obj.ifvoid = false;
}
if (obj.ifnull === undefined) {
obj.ifnull = false;
}
super(obj);
}
}
export class DotAssignee extends Node {
// `obj` has 5 properties: x, y, export, ifvoid, ifnull. `x` and `y` are mandatory,
// others are all optional defaulting to `false`.
constructor(obj) {
if (obj.export === undefined) {
obj.export = false;
}
if (obj.ifvoid === undefined) {
obj.ifvoid = false;
}
if (obj.ifnull === undefined) {
obj.ifnull = false;
}
super(obj);
}
}
export class BracketAssignees extends Arr {}
export class ArrowArgument extends Node {
// `obj` has 3 properties: variable, voidDefault, nullDefault. Only `variable` is mandatory,
// others are all optional defaulting to `null`.
constructor(obj) {
if (obj.voidDefault === undefined) {
obj.voidDefault = null;
}
if (obj.nullDefault === undefined) {
obj.nullDefault = null;
}
super(obj);
}
}
export class MemberKey extends Node {
// `obj` has 5 properties: name, new, static, get, set. All are optional.
// `name` defaults to `null`. Others defaults to `false`.
constructor(obj) {
if (obj.name === undefined) {
obj.name = null;
}
if (obj.new === undefined) {
obj.new = false;
}
if (obj.static === undefined) {
obj.static = false;
}
if (obj.get === undefined) {
obj.get = false;
}
if (obj.set === undefined) {
obj.set = false;
}
super(obj);
}
}
export class SymbolMemberName extends Piece {}
export class OrPattern extends Arr {}
export class AndPattern extends Arr {}
export class CompareOperator extends MappableNode {
toString() {
return $print.printAtom(this);
}
}
export class Equal extends CompareOperator {}
export class NotEqual extends CompareOperator {}
export class LessThan extends CompareOperator {}
export class GreaterThan extends CompareOperator {}
export class LessThanOrEqual extends CompareOperator {}
export class GreaterThanOrEqual extends CompareOperator {}
export class CallSpread extends MappableNode {
constructor(value) {
super();
this.value = value;
}
compile() {
return new J(["...", this.value.compile()]).shareLexPart(this);
}
}
export class NestedBracketAssigneesError extends $lex.SyntaxError {
constructor(lexPart) {
super(lexPart, "Destructuring assignment can't be nested.");
}
}
export class ExportWithIfvoidIfnullError extends $lex.SyntaxError {
constructor(lexPart) {
super(lexPart, "`'export` can't coexist with `ifvoid` and `ifnull`.");
}
}
export class BracketWithIfvoidIfnullError extends $lex.SyntaxError {
constructor(lexPart) {
super(lexPart, "Destructuring assignment can't coexist with `ifvoid` and `ifnull`.");
}
}
// This can build an assignee, or a `BracketAssignees` instance.
export let buildAssignee = (lexPart, ifvoidOn, ifnullOn, allowsBracket = true) => {
let lex = lexPart.lex;
let startIndex = lexPart.startIndex;
let endIndex = lexPart.endIndex;
let exportOn = false;
if (
lex.at(endIndex - 1) instanceof $lex.NormalVariant &&
lex.at(endIndex) instanceof $lex.NormalToken &&
lex.at(endIndex).value === "export"
) {
if (ifvoidOn || ifnullOn) {
throw new ExportWithIfvoidIfnullError(lex.part(endIndex, endIndex));
}
exportOn = true;
endIndex -= 2;
}
let dotMatch = $pattern.Pattern.matchPatternCapture(
[$pattern.tokens, $lex.Dot, $pattern.tokens],
lex.part(startIndex, endIndex),
false,
[0, 2]
);
if (dotMatch !== null) {
let y = null;
if (lex.at(dotMatch[1].startIndex) instanceof $lex.LeftParenthesis) {
y = $node.Expression.build(lex.part(dotMatch[1]).shrink());
}
else if (
(
lex.at(dotMatch[0].endIndex) instanceof $lex.Me ||
lex.at(dotMatch[0].endIndex) instanceof $lex.ClassMe
) &&
(
lex.at(dotMatch[1].startIndex) instanceof $lex.NormalToken &&
lex.at(dotMatch[1].startIndex).value.startsWith("_")
)
) {
y = new SymbolMemberName(lex.part(dotMatch[1]));
}
else {
y = new Piece(lex.part(dotMatch[1]));
}
return new DotAssignee({
x: $node.Expression.build(lex.part(dotMatch[0])),
y: y,
export: exportOn,
ifvoid: ifvoidOn,
ifnull: ifnullOn
});
}
else {
let bracketMatch = $pattern.Pattern.matchPatternCapture(
[$pattern.BracketPair],
lex.part(startIndex, endIndex),
true,
[0]
);
if (bracketMatch !== null) {
if (!allowsBracket) {
throw new NestedBracketAssigneesError(lex.part(startIndex, endIndex));
}
if (ifvoidOn || ifnullOn) {
throw new BracketWithIfvoidIfnullError(lex.part(startIndex, endIndex));
}
let elementRanges = $pattern.Pattern.split(
[$lex.Comma, $lex.Semicolon],
lex.part(bracketMatch[0]).shrink()
);
return new BracketAssignees(
elementRanges.map(m => buildAssignee(lex.part(m), ifvoidOn, ifnullOn, false))
);
}
else {
return new VariableAssignee({
variable: new LocalVariable(lexPart),
export: exportOn,
ifvoid: ifvoidOn,
ifnull: ifnullOn
});
}
}
};
// This can compile an assignee, or a `BracketAssignees` instance.
export let compileAssignee = (assignee) => {
let root = assignee.getRoot();
let first = null;
if (assignee instanceof $node.VariableAssignee && assignee.export) {
let external = assignee.export instanceof Piece ? assignee.export : assignee.variable;
if (
root.hasCompilerDirective("node modules") ||
root.hasCompilerDirective("node import")
) {
first = new J([
"exports.", external.compile()
]);
}
else {
root.loweredCompiled +=
"export {" + assignee.variable.compile().js + " as " + external.compile().js + "};";
}
}
let second = null;
if (assignee instanceof $node.BracketAssignees) {
second = new J([
"[",
new J(assignee.value.map(m => compileAssignee(m)), ","),
"]"
]);
}
else if (assignee instanceof $node.DotAssignee) {
let statement = assignee.getParent($node.Statement);
let useBase = statement instanceof $node.AssignStatement && statement.getUseBase();
let obj = useBase ? "base_" + $node.antiConflictString : assignee.x.compile();
if (
assignee.y instanceof $node.Expression ||
assignee.y instanceof $node.SymbolMemberName
) {
second = new J([obj, "[", assignee.y.compile(), "]"]);
}
else {
second = new J([obj, ".", assignee.y.compile()]);
}
}
else {
second = assignee.variable.compile();
}
if (first === null) {
return second;
}
else {
return new J([first, "=", second]);
}
};
export let compileWrapperFunction = (innerJb, node) => {
let ancestor = node.getParent($node.FunctionExpression);
if (ancestor !== null && ancestor.getAsync()) {
return new J([
`yield asyncToGenerator_${$node.antiConflictString}.call(this,function*(){`,
innerJb,
"})"
]);
}
else {
return new J(["(()=>{", innerJb, "})()"]);
}
};