@fable-org/fable-library-js
Version:
Core library used by F# projects compiled with fable.io
619 lines (618 loc) • 22 kB
JavaScript
/**
* F# Quotation runtime support for Fable JS/TS target.
*
* Provides class-based representations of F# quotation AST nodes
* and pattern matching helpers compatible with Fable's option convention
* (undefined = no match, value/tuple = match).
*/
// ===================================================================
// Var: represents an F# quotation variable
// ===================================================================
export class Var {
constructor(name, type_, isMutable) {
this.Name = name;
this.Type = type_;
this.IsMutable = isMutable;
}
}
export function mkQuotVar(name, type_, isMutable = false) {
return new Var(name, type_, isMutable);
}
export function varGetName(v) { return v.Name; }
export function varGetType(v) { return v.Type; }
export function varGetIsMutable(v) { return v.IsMutable; }
// ===================================================================
// Expr nodes: tagged classes for each quotation expression kind
// ===================================================================
export class ExprValue {
constructor(value, type) {
this.tag = "Value";
this.value = value;
this.type = type;
}
toJSON() { return ["Value", this.value, this.type]; }
}
export class ExprVarExpr {
constructor(var_) {
this.tag = "Var";
this.var_ = var_;
}
toJSON() { return ["Var", this.var_]; }
}
export class ExprLambda {
constructor(var_, body) {
this.tag = "Lambda";
this.var_ = var_;
this.body = body;
}
toJSON() { return ["Lambda", this.var_, this.body]; }
}
export class ExprApplication {
constructor(func, arg) {
this.tag = "Application";
this.func = func;
this.arg = arg;
}
toJSON() { return ["Application", this.func, this.arg]; }
}
export class ExprLet {
constructor(var_, value, body) {
this.tag = "Let";
this.var_ = var_;
this.value = value;
this.body = body;
}
toJSON() { return ["Let", this.var_, this.value, this.body]; }
}
export class ExprIfThenElse {
constructor(guard, thenExpr, elseExpr) {
this.tag = "IfThenElse";
this.guard = guard;
this.thenExpr = thenExpr;
this.elseExpr = elseExpr;
}
toJSON() { return ["IfThenElse", this.guard, this.thenExpr, this.elseExpr]; }
}
export class ExprCall {
constructor(instance, method, args) {
this.tag = "Call";
this.instance = instance;
this.method = method;
this.args = args;
}
toJSON() { return ["Call", this.instance, this.method, this.args]; }
}
export class ExprSequential {
constructor(first, second) {
this.tag = "Sequential";
this.first = first;
this.second = second;
}
toJSON() { return ["Sequential", this.first, this.second]; }
}
export class ExprNewTuple {
constructor(elements) {
this.tag = "NewTuple";
this.elements = elements;
}
toJSON() { return ["NewTuple", this.elements]; }
}
export class ExprNewUnion {
constructor(typeName, unionTag, fields) {
this.tag = "NewUnion";
this.typeName = typeName;
this.unionTag = unionTag;
this.fields = fields;
}
toJSON() { return ["NewUnion", this.typeName, this.unionTag, this.fields]; }
}
export class ExprNewRecord {
constructor(fieldNames, values) {
this.tag = "NewRecord";
this.fieldNames = fieldNames;
this.values = values;
}
toJSON() { return ["NewRecord", this.fieldNames, this.values]; }
}
export class ExprNewList {
constructor(head, tail) {
this.tag = "NewList";
this.head = head;
this.tail = tail;
}
toJSON() { return ["NewList", this.head, this.tail]; }
}
export class ExprTupleGet {
constructor(expr, index) {
this.tag = "TupleGet";
this.expr = expr;
this.index = index;
}
toJSON() { return ["TupleGet", this.expr, this.index]; }
}
export class ExprUnionTag {
constructor(expr) {
this.tag = "UnionTag";
this.expr = expr;
}
toJSON() { return ["UnionTag", this.expr]; }
}
export class ExprUnionField {
constructor(expr, fieldIndex) {
this.tag = "UnionField";
this.expr = expr;
this.fieldIndex = fieldIndex;
}
toJSON() { return ["UnionField", this.expr, this.fieldIndex]; }
}
export class ExprFieldGet {
constructor(expr, fieldName) {
this.tag = "FieldGet";
this.expr = expr;
this.fieldName = fieldName;
}
toJSON() { return ["FieldGet", this.expr, this.fieldName]; }
}
export class ExprFieldSet {
constructor(expr, fieldName, value) {
this.tag = "FieldSet";
this.expr = expr;
this.fieldName = fieldName;
this.value = value;
}
toJSON() { return ["FieldSet", this.expr, this.fieldName, this.value]; }
}
export class ExprVarSet {
constructor(target, value) {
this.tag = "VarSet";
this.target = target;
this.value = value;
}
toJSON() { return ["VarSet", this.target, this.value]; }
}
// ===================================================================
// Constructors (called by QuotationEmitter.fs)
// ===================================================================
export function mkValue(value, type) {
return new ExprValue(value, type);
}
export function mkVar(v) {
return new ExprVarExpr(v);
}
export function mkLambda(v, body) {
return new ExprLambda(v, body);
}
export function mkApplication(func, arg) {
return new ExprApplication(func, arg);
}
export function mkLet(v, value, body) {
return new ExprLet(v, value, body);
}
export function mkIfThenElse(guard, thenExpr, elseExpr) {
return new ExprIfThenElse(guard, thenExpr, elseExpr);
}
export function mkCall(instance, method, args) {
return new ExprCall(instance, method, args);
}
export function mkSequential(first, second) {
return new ExprSequential(first, second);
}
export function mkNewTuple(elements) {
return new ExprNewTuple(elements);
}
export function mkTupleGet(expr, index) {
return new ExprTupleGet(expr, index);
}
export function mkUnionTag(expr) {
return new ExprUnionTag(expr);
}
export function mkUnionField(expr, fieldIndex) {
return new ExprUnionField(expr, fieldIndex);
}
export function mkFieldGet(expr, fieldName) {
return new ExprFieldGet(expr, fieldName);
}
export function mkFieldSet(expr, fieldName, value) {
return new ExprFieldSet(expr, fieldName, value);
}
export function mkVarSet(target, value) {
return new ExprVarSet(target, value);
}
export function mkNewUnion(typeName, tag, fields) {
return new ExprNewUnion(typeName, tag, fields);
}
export function mkNewRecord(fieldNames, values) {
return new ExprNewRecord(fieldNames, values);
}
export function mkNewList(head, tail) {
return new ExprNewList(head, tail);
}
// ===================================================================
// Accessors
// ===================================================================
export function getType(expr) {
if (expr instanceof ExprValue)
return expr.type;
if (expr instanceof ExprLambda)
return expr.var_.Type;
return "obj";
}
// ===================================================================
// Pattern match helpers
// Returns undefined (no match) or a value/tuple (match), following
// Fable's option convention for active patterns.
// ===================================================================
export function isValue(expr) {
if (expr instanceof ExprValue)
return [expr.value, expr.type];
return undefined;
}
export function isVar(expr) {
if (expr instanceof ExprVarExpr)
return expr.var_;
return undefined;
}
export function isLambda(expr) {
if (expr instanceof ExprLambda)
return [expr.var_, expr.body];
return undefined;
}
export function isApplication(expr) {
if (expr instanceof ExprApplication)
return [expr.func, expr.arg];
return undefined;
}
export function isLet(expr) {
if (expr instanceof ExprLet)
return [expr.var_, expr.value, expr.body];
return undefined;
}
export function isIfThenElse(expr) {
if (expr instanceof ExprIfThenElse)
return [expr.guard, expr.thenExpr, expr.elseExpr];
return undefined;
}
export function isCall(expr) {
if (expr instanceof ExprCall)
return [expr.instance, expr.method, expr.args];
return undefined;
}
export function isSequential(expr) {
if (expr instanceof ExprSequential)
return [expr.first, expr.second];
return undefined;
}
export function isNewTuple(expr) {
if (expr instanceof ExprNewTuple)
return expr.elements;
return undefined;
}
export function isNewUnionCase(expr) {
if (expr instanceof ExprNewUnion)
return [expr.typeName, expr.fields];
return undefined;
}
export function isNewRecord(expr) {
if (expr instanceof ExprNewRecord)
return [expr.fieldNames, expr.values];
return undefined;
}
export function isTupleGet(expr) {
if (expr instanceof ExprTupleGet)
return [expr.expr, expr.index];
return undefined;
}
export function isFieldGet(expr) {
if (expr instanceof ExprFieldGet)
return [expr.expr, expr.fieldName];
return undefined;
}
// ===================================================================
// Evaluation
// ===================================================================
const OPERATORS = {
"op_Addition": (a, b) => a + b,
"op_Subtraction": (a, b) => a - b,
"op_Multiply": (a, b) => a * b,
"op_Division": (a, b) => a / b,
"op_Modulus": (a, b) => a % b,
"op_Exponentiation": (a, b) => a ** b,
"op_UnaryNegation": (a) => -a,
"op_UnaryPlus": (a) => +a,
"op_LogicalNot": (a) => !a,
"op_BitwiseOr": (a, b) => a | b,
"op_BitwiseAnd": (a, b) => a & b,
"op_ExclusiveOr": (a, b) => a ^ b,
"op_LeftShift": (a, b) => a << b,
"op_RightShift": (a, b) => a >> b,
"op_Equality": (a, b) => a === b,
"op_Inequality": (a, b) => a !== b,
"op_LessThan": (a, b) => a < b,
"op_LessThanOrEqual": (a, b) => a <= b,
"op_GreaterThan": (a, b) => a > b,
"op_GreaterThanOrEqual": (a, b) => a >= b,
"op_BooleanAnd": (a, b) => a && b,
"op_BooleanOr": (a, b) => a || b,
};
export function evaluate(expr, env) {
if (env == null)
env = new Map();
if (expr instanceof ExprValue)
return expr.value;
if (expr instanceof ExprVarExpr) {
if (env.has(expr.var_.Name))
return env.get(expr.var_.Name);
throw new Error(`Unbound variable: ${expr.var_.Name}`);
}
if (expr instanceof ExprLambda) {
const capturedEnv = new Map(env);
return (arg) => {
const newEnv = new Map(capturedEnv);
newEnv.set(expr.var_.Name, arg);
return evaluate(expr.body, newEnv);
};
}
if (expr instanceof ExprApplication) {
return evaluate(expr.func, env)(evaluate(expr.arg, env));
}
if (expr instanceof ExprLet) {
const newEnv = new Map(env);
newEnv.set(expr.var_.Name, evaluate(expr.value, env));
return evaluate(expr.body, newEnv);
}
if (expr instanceof ExprIfThenElse) {
return evaluate(expr.guard, env) ? evaluate(expr.thenExpr, env) : evaluate(expr.elseExpr, env);
}
if (expr instanceof ExprSequential) {
evaluate(expr.first, env);
return evaluate(expr.second, env);
}
if (expr instanceof ExprNewTuple) {
return expr.elements.map(e => evaluate(e, env));
}
if (expr instanceof ExprCall) {
const evaluatedArgs = expr.args.map((a) => evaluate(a, env));
if (expr.method in OPERATORS)
return OPERATORS[expr.method](...evaluatedArgs);
throw new Error(`Unknown method: ${expr.method}`);
}
if (expr instanceof ExprTupleGet) {
return evaluate(expr.expr, env)[expr.index];
}
if (expr instanceof ExprNewUnion) {
return [expr.unionTag, ...expr.fields.map((f) => evaluate(f, env))];
}
if (expr instanceof ExprNewRecord) {
const result = {};
for (let i = 0; i < expr.fieldNames.length; i++) {
result[expr.fieldNames[i]] = evaluate(expr.values[i], env);
}
return result;
}
if (expr instanceof ExprNewList) {
return [evaluate(expr.head, env), ...evaluate(expr.tail, env)];
}
if (expr instanceof ExprVarSet) {
if (expr.target instanceof ExprVarExpr) {
env.set(expr.target.var_.Name, evaluate(expr.value, env));
return undefined;
}
throw new Error("VarSet target must be a variable");
}
if (expr instanceof ExprFieldGet) {
const obj = evaluate(expr.expr, env);
return obj[expr.fieldName];
}
throw new Error(`Cannot evaluate expression: ${expr.tag}`);
}
// ===================================================================
// FSharpExpr instance methods
// ===================================================================
const OP_SYMBOLS = {
"op_Addition": "+",
"op_Subtraction": "-",
"op_Multiply": "*",
"op_Division": "/",
"op_Modulus": "%",
"op_Exponentiation": "**",
"op_Equality": "=",
"op_Inequality": "<>",
"op_LessThan": "<",
"op_LessThanOrEqual": "<=",
"op_GreaterThan": ">",
"op_GreaterThanOrEqual": ">=",
"op_BooleanAnd": "&&",
"op_BooleanOr": "||",
"op_UnaryNegation": "-",
"op_LogicalNot": "not",
};
export function exprToString(expr) {
if (expr instanceof ExprValue) {
if (expr.type === "string")
return `"${expr.value}"`;
if (expr.type === "unit")
return "()";
if (expr.type === "bool")
return expr.value ? "true" : "false";
return String(expr.value);
}
if (expr instanceof ExprVarExpr)
return expr.var_.Name;
if (expr instanceof ExprLambda)
return `fun ${expr.var_.Name} -> ${exprToString(expr.body)}`;
if (expr instanceof ExprApplication)
return `${exprToString(expr.func)} ${exprToString(expr.arg)}`;
if (expr instanceof ExprLet)
return `let ${expr.var_.Name} = ${exprToString(expr.value)} in ${exprToString(expr.body)}`;
if (expr instanceof ExprIfThenElse)
return `if ${exprToString(expr.guard)} then ${exprToString(expr.thenExpr)} else ${exprToString(expr.elseExpr)}`;
if (expr instanceof ExprCall) {
if (expr.method in OP_SYMBOLS && expr.args.length === 2) {
return `(${exprToString(expr.args[0])} ${OP_SYMBOLS[expr.method]} ${exprToString(expr.args[1])})`;
}
if (expr.method in OP_SYMBOLS && expr.args.length === 1) {
return `${OP_SYMBOLS[expr.method]}${exprToString(expr.args[0])}`;
}
return `${expr.method}(${expr.args.map(exprToString).join(", ")})`;
}
if (expr instanceof ExprSequential)
return `${exprToString(expr.first)}; ${exprToString(expr.second)}`;
if (expr instanceof ExprNewTuple)
return `(${expr.elements.map(exprToString).join(", ")})`;
if (expr instanceof ExprTupleGet)
return `Item${expr.index + 1}(${exprToString(expr.expr)})`;
if (expr instanceof ExprFieldGet)
return `${exprToString(expr.expr)}.${expr.fieldName}`;
return `<${expr.tag}>`;
}
export function getFreeVars(expr) {
const free = [];
const seen = new Set();
function walk(e, bound) {
if (e instanceof ExprVarExpr) {
if (!bound.has(e.var_.Name) && !seen.has(e.var_.Name)) {
free.push(e.var_);
seen.add(e.var_.Name);
}
}
else if (e instanceof ExprLambda) {
walk(e.body, new Set([...bound, e.var_.Name]));
}
else if (e instanceof ExprLet) {
walk(e.value, bound);
walk(e.body, new Set([...bound, e.var_.Name]));
}
else if (e instanceof ExprApplication) {
walk(e.func, bound);
walk(e.arg, bound);
}
else if (e instanceof ExprIfThenElse) {
walk(e.guard, bound);
walk(e.thenExpr, bound);
walk(e.elseExpr, bound);
}
else if (e instanceof ExprCall) {
for (const a of e.args)
walk(a, bound);
}
else if (e instanceof ExprSequential) {
walk(e.first, bound);
walk(e.second, bound);
}
else if (e instanceof ExprNewTuple) {
for (const el of e.elements)
walk(el, bound);
}
else if (e instanceof ExprTupleGet) {
walk(e.expr, bound);
}
else if (e instanceof ExprNewUnion) {
for (const f of e.fields)
walk(f, bound);
}
else if (e instanceof ExprNewRecord) {
for (const v of e.values)
walk(v, bound);
}
else if (e instanceof ExprNewList) {
walk(e.head, bound);
walk(e.tail, bound);
}
else if (e instanceof ExprUnionTag) {
walk(e.expr, bound);
}
else if (e instanceof ExprUnionField) {
walk(e.expr, bound);
}
else if (e instanceof ExprFieldGet) {
walk(e.expr, bound);
}
else if (e instanceof ExprFieldSet) {
walk(e.expr, bound);
walk(e.value, bound);
}
else if (e instanceof ExprVarSet) {
walk(e.target, bound);
walk(e.value, bound);
}
}
walk(expr, new Set());
return free;
}
export function substitute(expr, fn) {
function sub(e) {
if (e instanceof ExprVarExpr) {
const result = fn(e.var_);
return result !== undefined ? result : e;
}
if (e instanceof ExprLambda)
return new ExprLambda(e.var_, sub(e.body));
if (e instanceof ExprLet)
return new ExprLet(e.var_, sub(e.value), sub(e.body));
if (e instanceof ExprApplication)
return new ExprApplication(sub(e.func), sub(e.arg));
if (e instanceof ExprIfThenElse)
return new ExprIfThenElse(sub(e.guard), sub(e.thenExpr), sub(e.elseExpr));
if (e instanceof ExprCall) {
const newInst = e.instance != null ? sub(e.instance) : e.instance;
return new ExprCall(newInst, e.method, e.args.map(sub));
}
if (e instanceof ExprSequential)
return new ExprSequential(sub(e.first), sub(e.second));
if (e instanceof ExprNewTuple)
return new ExprNewTuple(e.elements.map(sub));
if (e instanceof ExprTupleGet)
return new ExprTupleGet(sub(e.expr), e.index);
if (e instanceof ExprNewUnion)
return new ExprNewUnion(e.typeName, e.unionTag, e.fields.map(sub));
if (e instanceof ExprNewRecord)
return new ExprNewRecord(e.fieldNames, e.values.map(sub));
if (e instanceof ExprNewList)
return new ExprNewList(sub(e.head), sub(e.tail));
if (e instanceof ExprUnionTag)
return new ExprUnionTag(sub(e.expr));
if (e instanceof ExprUnionField)
return new ExprUnionField(sub(e.expr), e.fieldIndex);
if (e instanceof ExprFieldGet)
return new ExprFieldGet(sub(e.expr), e.fieldName);
if (e instanceof ExprFieldSet)
return new ExprFieldSet(sub(e.expr), e.fieldName, sub(e.value));
if (e instanceof ExprVarSet)
return new ExprVarSet(sub(e.target), sub(e.value));
return e;
}
return sub(expr);
}
// ===================================================================
// JSON deserialization
// Reconstructs Expr/Var from the toJSON() array format.
// ===================================================================
function varFromJSON(json) {
return new Var(json.Name, json.Type, json.IsMutable);
}
export function exprFromJSON(json) {
if (!Array.isArray(json))
return new ExprValue(json, typeof json);
const [tag, ...fields] = json;
switch (tag) {
case "Value": return new ExprValue(fields[0], fields[1]);
case "Var": return new ExprVarExpr(varFromJSON(fields[0]));
case "Lambda": return new ExprLambda(varFromJSON(fields[0]), exprFromJSON(fields[1]));
case "Application": return new ExprApplication(exprFromJSON(fields[0]), exprFromJSON(fields[1]));
case "Let": return new ExprLet(varFromJSON(fields[0]), exprFromJSON(fields[1]), exprFromJSON(fields[2]));
case "IfThenElse": return new ExprIfThenElse(exprFromJSON(fields[0]), exprFromJSON(fields[1]), exprFromJSON(fields[2]));
case "Call": return new ExprCall(fields[0] != null ? exprFromJSON(fields[0]) : null, fields[1], fields[2].map(exprFromJSON));
case "Sequential": return new ExprSequential(exprFromJSON(fields[0]), exprFromJSON(fields[1]));
case "NewTuple": return new ExprNewTuple(fields[0].map(exprFromJSON));
case "TupleGet": return new ExprTupleGet(exprFromJSON(fields[0]), fields[1]);
case "NewUnion": return new ExprNewUnion(fields[0], fields[1], fields[2].map(exprFromJSON));
case "UnionTag": return new ExprUnionTag(exprFromJSON(fields[0]));
case "UnionField": return new ExprUnionField(exprFromJSON(fields[0]), fields[1]);
case "NewRecord": return new ExprNewRecord(fields[0], fields[1].map(exprFromJSON));
case "FieldGet": return new ExprFieldGet(exprFromJSON(fields[0]), fields[1]);
case "FieldSet": return new ExprFieldSet(exprFromJSON(fields[0]), fields[1], exprFromJSON(fields[2]));
case "VarSet": return new ExprVarSet(exprFromJSON(fields[0]), exprFromJSON(fields[1]));
case "NewList": return new ExprNewList(exprFromJSON(fields[0]), exprFromJSON(fields[1]));
default: return new ExprValue(json, "unknown");
}
}