time-to-botec

Benchmark sampling in different programming languages
Log | Files | Refs | README

index.ts (5400B)


      1 /*
      2  * An expression is an intermediate representation of a Squiggle code.
      3  * Expressions are evaluated by reducer's `evaluate` function.
      4  */
      5 import { ASTNode } from "../ast/parse.js";
      6 import { Value, vVoid } from "../value/index.js";
      7 
      8 export type LambdaExpressionParameter = {
      9   name: string;
     10   annotation?: Expression;
     11 };
     12 
     13 // All shapes are type+value, to help with V8 monomorphism.
     14 export type ExpressionContent =
     15   | {
     16       type: "Block";
     17       value: Expression[];
     18     }
     19   | {
     20       // Programs are similar to blocks, but they can export things for other modules to use.
     21       // There can be only one program at the top level of the expression.
     22       type: "Program";
     23       value: {
     24         statements: Expression[];
     25         exports: string[];
     26       };
     27     }
     28   | {
     29       type: "Array";
     30       value: Expression[];
     31     }
     32   | {
     33       type: "Dict";
     34       value: [Expression, Expression][];
     35     }
     36   | {
     37       type: "ResolvedSymbol";
     38       value: {
     39         name: string;
     40         // Position on stack, counting backwards (so last variable on stack has offset=0).
     41         // It's important to count backwards, because we want to store imports and continues on top of the stack.
     42         // (And maybe stdLib too, in the future.)
     43         offset: number;
     44       };
     45     }
     46   | {
     47       type: "Ternary";
     48       value: {
     49         condition: Expression;
     50         ifTrue: Expression;
     51         ifFalse: Expression;
     52       };
     53     }
     54   | {
     55       type: "Assign";
     56       value: {
     57         left: string;
     58         right: Expression;
     59       };
     60     }
     61   | {
     62       type: "Call";
     63       value: {
     64         fn: Expression;
     65         args: Expression[];
     66       };
     67     }
     68   | {
     69       type: "Lambda";
     70       value: {
     71         name?: string;
     72         parameters: LambdaExpressionParameter[];
     73         body: Expression;
     74       };
     75     }
     76   | {
     77       type: "Value";
     78       value: Value;
     79     };
     80 
     81 export type Expression = ExpressionContent & { ast: ASTNode };
     82 
     83 export const eArray = (anArray: Expression[]): ExpressionContent => ({
     84   type: "Array",
     85   value: anArray,
     86 });
     87 
     88 export const eValue = (value: Value): ExpressionContent => ({
     89   type: "Value",
     90   value,
     91 });
     92 
     93 export const eCall = (
     94   fn: Expression,
     95   args: Expression[]
     96 ): ExpressionContent => ({
     97   type: "Call",
     98   value: {
     99     fn,
    100     args,
    101   },
    102 });
    103 
    104 export const eLambda = (
    105   name: string | undefined,
    106   parameters: LambdaExpressionParameter[],
    107   body: Expression
    108 ): ExpressionContent => ({
    109   type: "Lambda",
    110   value: {
    111     name,
    112     parameters,
    113     body,
    114   },
    115 });
    116 
    117 export const eDict = (aMap: [Expression, Expression][]): ExpressionContent => ({
    118   type: "Dict",
    119   value: aMap,
    120 });
    121 
    122 export const eResolvedSymbol = (
    123   name: string,
    124   offset: number
    125 ): ExpressionContent => ({
    126   type: "ResolvedSymbol",
    127   value: { name, offset },
    128 });
    129 
    130 export const eBlock = (exprs: Expression[]): ExpressionContent => ({
    131   type: "Block",
    132   value: exprs,
    133 });
    134 
    135 export const eProgram = (
    136   statements: Expression[],
    137   exports: string[]
    138 ): ExpressionContent => ({
    139   type: "Program",
    140   value: {
    141     statements,
    142     exports,
    143   },
    144 });
    145 
    146 export const eLetStatement = (
    147   left: string,
    148   right: Expression
    149 ): ExpressionContent => ({
    150   type: "Assign",
    151   value: {
    152     left,
    153     right,
    154   },
    155 });
    156 
    157 export const eTernary = (
    158   condition: Expression,
    159   ifTrue: Expression,
    160   ifFalse: Expression
    161 ): ExpressionContent => ({
    162   type: "Ternary",
    163   value: {
    164     condition,
    165     ifTrue,
    166     ifFalse,
    167   },
    168 });
    169 
    170 export const eVoid = (): ExpressionContent => ({
    171   type: "Value",
    172   value: vVoid(),
    173 });
    174 
    175 // Converts the expression to String. Useful for tests.
    176 export function expressionToString(expression: Expression): string {
    177   switch (expression.type) {
    178     case "Block":
    179       return `{${expression.value.map(expressionToString).join("; ")}}`;
    180     case "Program": {
    181       const exports = new Set<string>(expression.value.exports);
    182       return expression.value.statements
    183         .map((statement) => {
    184           const statementString = expressionToString(statement);
    185           return statement.type === "Assign" &&
    186             exports.has(statement.value.left)
    187             ? `export ${statementString}`
    188             : statementString;
    189         })
    190         .join("; ");
    191     }
    192     case "Array":
    193       return `[${expression.value.map(expressionToString).join(", ")}]`;
    194     case "Dict":
    195       return `{${expression.value
    196         .map(
    197           ([key, value]) =>
    198             `${expressionToString(key)}: ${expressionToString(value)}`
    199         )
    200         .join(", ")}}`;
    201     case "ResolvedSymbol":
    202       // it would be useful to output the offset here, but we need to update tests accordingly
    203       return expression.value.name;
    204     case "Ternary":
    205       return `${expressionToString(
    206         expression.value.condition
    207       )} ? (${expressionToString(
    208         expression.value.ifTrue
    209       )}) : (${expressionToString(expression.value.ifFalse)})`;
    210     case "Assign":
    211       return `${expression.value.left} = ${expressionToString(
    212         expression.value.right
    213       )}`;
    214     case "Call":
    215       return `(${expressionToString(
    216         expression.value.fn
    217       )})(${expression.value.args.map(expressionToString).join(", ")})`;
    218     case "Lambda":
    219       return `{|${expression.value.parameters
    220         .map((parameter) => parameter.name)
    221         .join(", ")}| ${expressionToString(expression.value.body)}}`;
    222     case "Value":
    223       return expression.value.toString();
    224     default:
    225       return `Unknown expression ${expression satisfies never}`;
    226   }
    227 }