From 3e7c7b43d6fea891b7c9c63b35c009c300490df3 Mon Sep 17 00:00:00 2001 From: Byakuren Hijiri Date: Fri, 12 Jul 2024 11:10:34 +0000 Subject: [PATCH] feat: Initial draft of the new backend. + Func syntax tree + Essential functionality Func formatter + A new `codegen` package that includes the updated backend + Hacks in the compilation pipeline that allow to use both backends which is needed for differential testing. --- src/codegen/abi.ts | 26 ++ src/codegen/contract.ts | 103 ++++++ src/codegen/expression.ts | 733 ++++++++++++++++++++++++++++++++++++++ src/codegen/function.ts | 291 +++++++++++++++ src/codegen/index.ts | 4 + src/codegen/statement.ts | 498 ++++++++++++++++++++++++++ src/codegen/type.ts | 194 ++++++++++ src/codegen/util.ts | 57 +++ src/func/formatter.ts | 322 +++++++++++++++++ src/func/syntax.ts | 343 ++++++++++++++++++ src/pipeline/build.ts | 2 +- src/pipeline/compile.ts | 45 ++- 12 files changed, 2610 insertions(+), 8 deletions(-) create mode 100644 src/codegen/abi.ts create mode 100644 src/codegen/contract.ts create mode 100644 src/codegen/expression.ts create mode 100644 src/codegen/function.ts create mode 100644 src/codegen/index.ts create mode 100644 src/codegen/statement.ts create mode 100644 src/codegen/type.ts create mode 100644 src/codegen/util.ts create mode 100644 src/func/formatter.ts create mode 100644 src/func/syntax.ts diff --git a/src/codegen/abi.ts b/src/codegen/abi.ts new file mode 100644 index 000000000..55ec6a956 --- /dev/null +++ b/src/codegen/abi.ts @@ -0,0 +1,26 @@ +import { AstExpression, SrcInfo } from "../grammar/ast"; +import { CompilerContext } from "../context"; +import { TypeRef } from "../types/types"; +import { FuncAstExpr } from "../func/syntax"; + +/** + * A static map of functions defining Func expressions for Tact ABI functions and methods. + */ +export type AbiFunction = { + name: string; + resolve: (ctx: CompilerContext, args: TypeRef[], loc: SrcInfo) => TypeRef; + generate: ( + args: TypeRef[], + resolved: AstExpression[], + loc: SrcInfo, + ) => FuncAstExpr; +}; + +// TODO +export const MapFunctions: Map = new Map([]); + +// TODO +export const StructFunctions: Map = new Map([]); + +// TODO +export const GlobalFunctions: Map = new Map([]); diff --git a/src/codegen/contract.ts b/src/codegen/contract.ts new file mode 100644 index 000000000..e327f19f7 --- /dev/null +++ b/src/codegen/contract.ts @@ -0,0 +1,103 @@ +import { CompilerContext } from "../context"; +import { getAllTypes } from "../types/resolveDescriptors"; +import { TypeDescription } from "../types/types"; +import { getSortedTypes } from "../storage/resolveAllocation"; +import { FuncAstModule, FuncAstComment } from "../func/syntax"; +import { FunctionGen } from "./function"; + +/** + * Encapsulates generation of Func contracts from the Tact contract. + */ +export class ContractGen { + private constructor( + private ctx: CompilerContext, + private contractName: string, + private abiName: string, + ) {} + + static fromTact( + ctx: CompilerContext, + contractName: string, + abiName: string, + ): ContractGen { + return new ContractGen(ctx, contractName, abiName); + } + + /** + * Adds stdlib definitions to the generated module. + */ + private addStdlib(m: FuncAstModule): void { + // TODO + } + + private addSerializers(m: FuncAstModule): void { + const sortedTypes = getSortedTypes(this.ctx); + for (const t of sortedTypes) { + } + } + + private addAccessors(m: FuncAstModule): void { + // TODO + } + + private addInitSerializer(m: FuncAstModule): void { + // TODO + } + + private addStorageFunctions(m: FuncAstModule): void { + // TODO + } + + private addStaticFunctions(m: FuncAstModule): void { + // TODO + } + + private addExtensions(m: FuncAstModule): void { + // TODO + } + + /** + * Adds functions defined within the Tact contract to the generated Func module. + * TODO: Why do we need function from *all* the contracts? + */ + private addContractFunctions(m: FuncAstModule, c: TypeDescription): void { + // TODO: Generate init + for (const tactFun of c.functions.values()) { + const funcFun = FunctionGen.fromTact(this.ctx, tactFun).generate(); + } + } + + private addMain(m: FuncAstModule): void { + // TODO + } + + /** + * Adds a comment entry to the module. + */ + private addComment(m: FuncAstModule, c: FuncAstComment): void { + m.entries.push(c); + } + + public generate(): FuncAstModule { + const m: FuncAstModule = { kind: "module", entries: [] }; + + const allTypes = Object.values(getAllTypes(this.ctx)); + const contracts = allTypes.filter((v) => v.kind === "contract"); + const contract = contracts.find((v) => v.name === this.contractName); + if (contract === undefined) { + throw Error(`Contract "${this.contractName}" not found`); + } + + this.addStdlib(m); + this.addSerializers(m); + this.addAccessors(m); + this.addInitSerializer(m); + this.addStorageFunctions(m); + this.addStaticFunctions(m); + this.addExtensions(m); + contracts.forEach((c) => this.addContractFunctions(m, c)); + this.addMain(m); + + return m; + } +} diff --git a/src/codegen/expression.ts b/src/codegen/expression.ts new file mode 100644 index 000000000..cf1bc4638 --- /dev/null +++ b/src/codegen/expression.ts @@ -0,0 +1,733 @@ +import { CompilerContext } from "../context"; +import { TactConstEvalError, throwCompilationError } from "../errors"; +import { evalConstantExpression } from "../constEval"; +import { resolveFuncTypeUnpack } from "./type"; +import { MapFunctions, StructFunctions, GlobalFunctions } from "./abi"; +import { getExpType } from "../types/resolveExpression"; +import { cast, funcIdOf, ops } from "./util"; +import { printTypeRef, TypeRef, Value } from "../types/types"; +import { + getStaticConstant, + getType, + getStaticFunction, + hasStaticConstant, +} from "../types/resolveDescriptors"; +import { idText, AstExpression } from "../grammar/ast"; +import { FuncAstExpr, FuncAstIdExpr, FuncAstCallExpr } from "../func/syntax"; + +function isNull(f: AstExpression): boolean { + return f.kind === "null"; +} + +/** + * Encapsulates generation of Func expressions from the Tact expression. + */ +export class ExpressionGen { + /** + * @param tactExpr Expression to translate. + */ + private constructor( + private ctx: CompilerContext, + private tactExpr: AstExpression, + ) {} + + static fromTact( + ctx: CompilerContext, + tactExpr: AstExpression, + ): ExpressionGen { + return new ExpressionGen(ctx, tactExpr); + } + + /*** + * Generates FunC literals from Tact ones. + */ + private writeValue(val: Value): FuncAstExpr { + if (typeof val === "bigint") { + return { kind: "number_expr", value: val }; + } + // if (typeof val === "string") { + // const id = writeString(val, wCtx); + // wCtx.used(id); + // return `${id}()`; + // } + if (typeof val === "boolean") { + return { kind: "bool_expr", value: val }; + } + // if (Address.isAddress(val)) { + // const res = writeAddress(val, wCtx); + // wCtx.used(res); + // return res + "()"; + // } + // if (val instanceof Cell) { + // const res = writeCell(val, wCtx); + // wCtx.used(res); + // return `${res}()`; + // } + if (val === null) { + return { kind: "nil_expr" }; + } + // if (val instanceof CommentValue) { + // const id = writeComment(val.comment, wCtx); + // wCtx.used(id); + // return `${id}()`; + // } + // if (typeof val === "object" && "$tactStruct" in val) { + // // this is a struct value + // const structDescription = getType( + // wCtx.ctx, + // val["$tactStruct"] as string, + // ); + // const fields = structDescription.fields.map((field) => field.name); + // const id = writeStructConstructor(structDescription, fields, wCtx); + // wCtx.used(id); + // const fieldValues = structDescription.fields.map((field) => { + // if (field.name in val) { + // return writeValue(val[field.name]!, wCtx); + // } else { + // throw Error( + // `Struct value is missing a field: ${field.name}`, + // val, + // ); + // } + // }); + // return `${id}(${fieldValues.join(", ")})`; + // } + throw Error(`Invalid value: ${val}`); + } + + public writeExpression(): FuncAstExpr { + // literals and constant expressions are covered here + try { + const value = evalConstantExpression(this.tactExpr, this.ctx); + return this.writeValue(value); + } catch (error) { + if (!(error instanceof TactConstEvalError) || error.fatal) + throw error; + } + + // + // ID Reference + // + if (this.tactExpr.kind === "id") { + const t = getExpType(this.ctx, this.tactExpr); + + // Handle packed type + if (t.kind === "ref") { + const tt = getType(this.ctx, t.name); + if (tt.kind === "contract" || tt.kind === "struct") { + const value = resolveFuncTypeUnpack( + this.ctx, + t, + funcIdOf(this.tactExpr.text), + ); + return { kind: "id_expr", value }; + } + } + + if (t.kind === "ref_bounced") { + const tt = getType(this.ctx, t.name); + if (tt.kind === "struct") { + const value = resolveFuncTypeUnpack( + this.ctx, + t, + funcIdOf(this.tactExpr.text), + false, + true, + ); + } + } + + // Handle constant + if (hasStaticConstant(this.ctx, this.tactExpr.text)) { + const c = getStaticConstant(this.ctx, this.tactExpr.text); + return this.writeValue(c.value!); + } + + const value = funcIdOf(this.tactExpr.text); + return { kind: "id_expr", value }; + } + + // NOTE: We always wrap in parentheses to avoid operator precedence issues + if (this.tactExpr.kind === "op_binary") { + const negate: (expr: FuncAstExpr) => FuncAstExpr = (expr) => ({ + kind: "unary_expr", + op: "~", + value: expr, + }); + + // Special case for non-integer types and nullable + if (this.tactExpr.op === "==" || this.tactExpr.op === "!=") { + // TODO: Simplify. + if (isNull(this.tactExpr.left) && isNull(this.tactExpr.right)) { + return { + kind: "bool_expr", + value: this.tactExpr.op === "==", + }; + } else if ( + isNull(this.tactExpr.left) && + !isNull(this.tactExpr.right) + ) { + const fun = { kind: "id_expr", value: "null?" }; + const args = [ + ExpressionGen.fromTact( + this.ctx, + this.tactExpr.right, + ).writeExpression(), + ]; + const call = { + kind: "call_expr", + fun, + args, + } as FuncAstCallExpr; + return this.tactExpr.op === "==" ? call : negate(call); + } else if ( + !isNull(this.tactExpr.left) && + isNull(this.tactExpr.right) + ) { + const fun = { kind: "id_expr", value: "null?" }; + const args = [ + ExpressionGen.fromTact( + this.ctx, + this.tactExpr.left, + ).writeExpression(), + ]; + const call = { + kind: "call_expr", + fun, + args, + } as FuncAstCallExpr; + return this.tactExpr.op === "==" ? call : negate(call); + } + } + + // Special case for address + const lt = getExpType(this.ctx, this.tactExpr.left); + const rt = getExpType(this.ctx, this.tactExpr.right); + + // Case for addresses equality + if ( + lt.kind === "ref" && + rt.kind === "ref" && + lt.name === "Address" && + rt.name === "Address" + ) { + if (lt.optional && rt.optional) { + // wCtx.used(`__tact_slice_eq_bits_nullable`); + const fun = { + kind: "id_expr", + value: "__tact_slice_eq_bits_nullable", + }; + const args = [ + ExpressionGen.fromTact( + this.ctx, + this.tactExpr.left, + ).writeExpression(), + ExpressionGen.fromTact( + this.ctx, + this.tactExpr.right, + ).writeExpression(), + ]; + const call = { + kind: "call_expr", + fun, + args, + } as FuncAstCallExpr; + return this.tactExpr.op == "!=" ? negate(call) : call; + } + // if (lt.optional && !rt.optional) { + // // wCtx.used(`__tact_slice_eq_bits_nullable_one`); + // return `( ${prefix}__tact_slice_eq_bits_nullable_one(${writeExpression(f.left, wCtx)}, ${writeExpression(f.right, wCtx)}) )`; + // } + // if (!lt.optional && rt.optional) { + // // wCtx.used(`__tact_slice_eq_bits_nullable_one`); + // return `( ${prefix}__tact_slice_eq_bits_nullable_one(${writeExpression(f.right, wCtx)}, ${writeExpression(f.left, wCtx)}) )`; + // } + // // wCtx.used(`__tact_slice_eq_bits`); + // return `( ${prefix}__tact_slice_eq_bits(${writeExpression(f.right, wCtx)}, ${writeExpression(f.left, wCtx)}) )`; + // } + // + // // Case for cells equality + // if ( + // lt.kind === "ref" && + // rt.kind === "ref" && + // lt.name === "Cell" && + // rt.name === "Cell" + // ) { + // const op = f.op === "==" ? "eq" : "neq"; + // if (lt.optional && rt.optional) { + // wCtx.used(`__tact_cell_${op}_nullable`); + // return `__tact_cell_${op}_nullable(${writeExpression(f.left, wCtx)}, ${writeExpression(f.right, wCtx)})`; + // } + // if (lt.optional && !rt.optional) { + // wCtx.used(`__tact_cell_${op}_nullable_one`); + // return `__tact_cell_${op}_nullable_one(${writeExpression(f.left, wCtx)}, ${writeExpression(f.right, wCtx)})`; + // } + // if (!lt.optional && rt.optional) { + // wCtx.used(`__tact_cell_${op}_nullable_one`); + // return `__tact_cell_${op}_nullable_one(${writeExpression(f.right, wCtx)}, ${writeExpression(f.left, wCtx)})`; + // } + // wCtx.used(`__tact_cell_${op}`); + // return `__tact_cell_${op}(${writeExpression(f.right, wCtx)}, ${writeExpression(f.left, wCtx)})`; + // } + // + // // Case for slices and strings equality + // if ( + // lt.kind === "ref" && + // rt.kind === "ref" && + // lt.name === rt.name && + // (lt.name === "Slice" || lt.name === "String") + // ) { + // const op = f.op === "==" ? "eq" : "neq"; + // if (lt.optional && rt.optional) { + // wCtx.used(`__tact_slice_${op}_nullable`); + // return `__tact_slice_${op}_nullable(${writeExpression(f.left, wCtx)}, ${writeExpression(f.right, wCtx)})`; + // } + // if (lt.optional && !rt.optional) { + // wCtx.used(`__tact_slice_${op}_nullable_one`); + // return `__tact_slice_${op}_nullable_one(${writeExpression(f.left, wCtx)}, ${writeExpression(f.right, wCtx)})`; + // } + // if (!lt.optional && rt.optional) { + // wCtx.used(`__tact_slice_${op}_nullable_one`); + // return `__tact_slice_${op}_nullable_one(${writeExpression(f.right, wCtx)}, ${writeExpression(f.left, wCtx)})`; + // } + // wCtx.used(`__tact_slice_${op}`); + // return `__tact_slice_${op}(${writeExpression(f.right, wCtx)}, ${writeExpression(f.left, wCtx)})`; + // } + // + // // Case for maps equality + // if (lt.kind === "map" && rt.kind === "map") { + // const op = f.op === "==" ? "eq" : "neq"; + // wCtx.used(`__tact_cell_${op}_nullable`); + // return `__tact_cell_${op}_nullable(${writeExpression(f.left, wCtx)}, ${writeExpression(f.right, wCtx)})`; + // } + // + // // Check for int or boolean types + // if ( + // lt.kind !== "ref" || + // rt.kind !== "ref" || + // (lt.name !== "Int" && lt.name !== "Bool") || + // (rt.name !== "Int" && rt.name !== "Bool") + // ) { + // const file = f.loc.file; + // const loc_info = f.loc.interval.getLineAndColumn(); + // throw Error( + // `(Internal Compiler Error) Invalid types for binary operation: ${file}:${loc_info.lineNum}:${loc_info.colNum}`, + // ); // Should be unreachable + // } + // + // // Case for ints equality + // if (f.op === "==" || f.op === "!=") { + // const op = f.op === "==" ? "eq" : "neq"; + // if (lt.optional && rt.optional) { + // wCtx.used(`__tact_int_${op}_nullable`); + // return `__tact_int_${op}_nullable(${writeExpression(f.left, wCtx)}, ${writeExpression(f.right, wCtx)})`; + // } + // if (lt.optional && !rt.optional) { + // wCtx.used(`__tact_int_${op}_nullable_one`); + // return `__tact_int_${op}_nullable_one(${writeExpression(f.left, wCtx)}, ${writeExpression(f.right, wCtx)})`; + // } + // if (!lt.optional && rt.optional) { + // wCtx.used(`__tact_int_${op}_nullable_one`); + // return `__tact_int_${op}_nullable_one(${writeExpression(f.right, wCtx)}, ${writeExpression(f.left, wCtx)})`; + // } + // if (f.op === "==") { + // return `(${writeExpression(f.left, wCtx)} == ${writeExpression(f.right, wCtx)})`; + // } else { + // return `(${writeExpression(f.left, wCtx)} != ${writeExpression(f.right, wCtx)})`; + // } + // } + // + // // Case for "&&" operator + // if (f.op === "&&") { + // return `( (${writeExpression(f.left, wCtx)}) ? (${writeExpression(f.right, wCtx)}) : (false) )`; + // } + // + // // Case for "||" operator + // if (f.op === "||") { + // return `( (${writeExpression(f.left, wCtx)}) ? (true) : (${writeExpression(f.right, wCtx)}) )`; + // } + // + // // Other ops + // return ( + // "(" + + // writeExpression(f.left, wCtx) + + // " " + + // f.op + + // " " + + // writeExpression(f.right, wCtx) + + // ")" + // ); + throw new Error("NYI"); + } + } + + // // + // // Unary operations: !, -, +, !! + // // NOTE: We always wrap in parenthesis to avoid operator precedence issues + // // + // + // if (f.kind === "op_unary") { + // // NOTE: Logical not is written as a bitwise not + // switch (f.op) { + // case "!": { + // return "(~ " + writeExpression(f.operand, wCtx) + ")"; + // } + // + // case "~": { + // return "(~ " + writeExpression(f.operand, wCtx) + ")"; + // } + // + // case "-": { + // return "(- " + writeExpression(f.operand, wCtx) + ")"; + // } + // + // case "+": { + // return "(+ " + writeExpression(f.operand, wCtx) + ")"; + // } + // + // // NOTE: Assert function that ensures that the value is not null + // case "!!": { + // const t = getExpType(wCtx.ctx, f.operand); + // if (t.kind === "ref") { + // const tt = getType(wCtx.ctx, t.name); + // if (tt.kind === "struct") { + // return `${ops.typeNotNull(tt.name, wCtx)}(${writeExpression(f.operand, wCtx)})`; + // } + // } + // + // wCtx.used("__tact_not_null"); + // return `${wCtx.used("__tact_not_null")}(${writeExpression(f.operand, wCtx)})`; + // } + // } + // } + // + // // + // // Field Access + // // NOTE: this branch resolves "a.b", where "a" is an expression and "b" is a field name + // // + // + // if (f.kind === "field_access") { + // // Resolve the type of the expression + // const src = getExpType(wCtx.ctx, f.aggregate); + // if ( + // (src.kind !== "ref" || src.optional) && + // src.kind !== "ref_bounced" + // ) { + // throwCompilationError( + // `Cannot access field of non-struct type: "${printTypeRef(src)}"`, + // f.loc, + // ); + // } + // const srcT = getType(wCtx.ctx, src.name); + // + // // Resolve field + // let fields: FieldDescription[]; + // + // fields = srcT.fields; + // if (src.kind === "ref_bounced") { + // fields = fields.slice(0, srcT.partialFieldCount); + // } + // + // const field = fields.find((v) => eqNames(v.name, f.field)); + // const cst = srcT.constants.find((v) => eqNames(v.name, f.field)); + // if (!field && !cst) { + // throwCompilationError( + // `Cannot find field ${idTextErr(f.field)} in struct ${idTextErr(srcT.name)}`, + // f.field.loc, + // ); + // } + // + // if (field) { + // // Trying to resolve field as a path + // const path = tryExtractPath(f); + // if (path) { + // // Prepare path + // const idd = writePathExpression(path); + // + // // Special case for structs + // if (field.type.kind === "ref") { + // const ft = getType(wCtx.ctx, field.type.name); + // if (ft.kind === "struct" || ft.kind === "contract") { + // return resolveFuncTypeUnpack(field.type, idd, wCtx); + // } + // } + // + // return idd; + // } + // + // // Getter instead of direct field access + // return `${ops.typeField(srcT.name, field.name, wCtx)}(${writeExpression(f.aggregate, wCtx)})`; + // } else { + // return writeValue(cst!.value!, wCtx); + // } + // } + // + + // + // Static Function Call + // + if (this.tactExpr.kind === "static_call") { + // Check global functions + if (GlobalFunctions.has(idText(this.tactExpr.function))) { + return GlobalFunctions.get( + idText(this.tactExpr.function), + )!.generate( + this.tactExpr.args.map((v) => getExpType(this.ctx, v)), + this.tactExpr.args, + this.tactExpr.loc, + ); + } + + const sf = getStaticFunction( + this.ctx, + idText(this.tactExpr.function), + ); + // if (sf.ast.kind === "native_function_decl") { + // n = idText(sf.ast.nativeName); + // if (n.startsWith("__tact")) { + // // wCtx.used(n); + // } + // } else { + // // wCtx.used(n); + // } + const fun = { + kind: "id_expr", + value: ops.global(idText(this.tactExpr.function)), + } as FuncAstIdExpr; + const args = this.tactExpr.args.map((argAst, i) => + ExpressionGen.fromTact(this.ctx, argAst).writeCastedExpression( + sf.params[i]!.type, + ), + ); + return { kind: "call_expr", fun, args }; + } + + // + // // + // // Struct Constructor + // // + // + // if (f.kind === "struct_instance") { + // const src = getType(wCtx.ctx, f.type); + // + // // Write a constructor + // const id = writeStructConstructor( + // src, + // f.args.map((v) => idText(v.field)), + // wCtx, + // ); + // wCtx.used(id); + // + // // Write an expression + // const expressions = f.args.map( + // (v) => + // writeCastedExpression( + // v.initializer, + // src.fields.find((v2) => eqNames(v2.name, v.field))! + // .type, + // wCtx, + // ), + // wCtx, + // ); + // return `${id}(${expressions.join(", ")})`; + // } + // + // + // Object-based function call + // + if (this.tactExpr.kind === "method_call") { + // Resolve source type + const src = getExpType(this.ctx, this.tactExpr.self); + + // Reference type + if (src.kind === "ref") { + if (src.optional) { + throwCompilationError( + `Cannot call function of non - direct type: "${printTypeRef(src)}"`, + this.tactExpr.loc, + ); + } + + // Render function call + const methodTy = getType(this.ctx, src.name); + + // Check struct ABI + if (methodTy.kind === "struct") { + if (StructFunctions.has(idText(this.tactExpr.method))) { + console.log(`getting ${idText(this.tactExpr.method)}`); + const abi = StructFunctions.get( + idText(this.tactExpr.method), + )!; + // return abi.generate( + // wCtx, + // [ + // src, + // ...this.tactExpr.args.map((v) => getExpType(this.ctx, v)), + // ], + // [this.tactExpr.self, ...this.tactExpr.args], + // this.tactExpr.loc, + // ); + } + } + + // Resolve function + const methodFun = methodTy.functions.get( + idText(this.tactExpr.method), + )!; + let name = ops.extension( + src.name, + idText(this.tactExpr.method), + ); + if ( + methodFun.ast.kind === "function_def" || + methodFun.ast.kind === "function_decl" + ) { + // wCtx.used(name); + } else { + name = idText(methodFun.ast.nativeName); + if (name.startsWith("__tact")) { + // wCtx.used(name); + } + } + + // Translate arguments + let argExprs = this.tactExpr.args.map((a, i) => + ExpressionGen.fromTact(this.ctx, a).writeCastedExpression( + methodFun.params[i]!.type, + ), + ); + + // Hack to replace a single struct argument to a tensor wrapper since otherwise + // func would convert (int) type to just int and break mutating functions + if (methodFun.isMutating) { + if (this.tactExpr.args.length === 1) { + const t = getExpType(this.ctx, this.tactExpr.args[0]!); + if (t.kind === "ref") { + const tt = getType(this.ctx, t.name); + if ( + (tt.kind === "contract" || + tt.kind === "struct") && + methodFun.params[0]!.type.kind === "ref" && + !methodFun.params[0]!.type.optional + ) { + const fun = { + kind: "id_expr", + value: ops.typeTensorCast(tt.name), + } as FuncAstIdExpr; + argExprs = [ + { + kind: "call_expr", + fun, + args: [argExprs[0]!], + }, + ]; + } + } + } + } + + // Generate function call + const selfExpr = ExpressionGen.fromTact( + this.ctx, + this.tactExpr.self, + ).writeExpression(); + if (methodFun.isMutating) { + if ( + this.tactExpr.self.kind === "id" || + this.tactExpr.self.kind === "field_access" + ) { + if (selfExpr.kind !== "id_expr") { + throw new Error( + `Impossible self kind: ${selfExpr.kind}`, + ); + } + const fun = { + kind: "id_expr", + value: `${selfExpr}~${name}`, + } as FuncAstIdExpr; + return { kind: "call_expr", fun, args: argExprs }; + } else { + const fun = { + kind: "id_expr", + value: ops.nonModifying(name), + } as FuncAstIdExpr; + return { + kind: "call_expr", + fun, + args: [selfExpr, ...argExprs], + }; + } + } else { + const fun = { + kind: "id_expr", + value: name, + } as FuncAstIdExpr; + return { + kind: "call_expr", + fun, + args: [selfExpr, ...argExprs], + }; + } + } + + // Map types + if (src.kind === "map") { + if (!MapFunctions.has(idText(this.tactExpr.method))) { + throwCompilationError( + `Map function "${idText(this.tactExpr.method)}" not found`, + this.tactExpr.loc, + ); + } + const abf = MapFunctions.get(idText(this.tactExpr.method))!; + return abf.generate( + [ + src, + ...this.tactExpr.args.map((v) => + getExpType(this.ctx, v), + ), + ], + [this.tactExpr.self, ...this.tactExpr.args], + this.tactExpr.loc, + ); + } + + if (src.kind === "ref_bounced") { + throw Error("Unimplemented"); + } + + throwCompilationError( + `Cannot call function of non - direct type: "${printTypeRef(src)}"`, + this.tactExpr.loc, + ); + } + + // + // // + // // Init of + // // + // + // if (f.kind === "init_of") { + // const type = getType(wCtx.ctx, f.contract); + // return `${ops.contractInitChild(idText(f.contract), wCtx)}(${["__tact_context_sys", ...f.args.map((a, i) => writeCastedExpression(a, type.init!.params[i]!.type, wCtx))].join(", ")})`; + // } + // + // // + // // Ternary operator + // // + // + // if (f.kind === "conditional") { + // return `(${writeExpression(f.condition, wCtx)} ? ${writeExpression(f.thenBranch, wCtx)} : ${writeExpression(f.elseBranch, wCtx)})`; + // } + // + // // + // // Unreachable + // // + // + throw Error(`Unknown expression: ${this.tactExpr.kind}`); + } + + public writeCastedExpression(to: TypeRef): FuncAstExpr { + const expr = getExpType(this.ctx, this.tactExpr); + return cast(this.ctx, expr, to, this.writeExpression()); + } +} diff --git a/src/codegen/function.ts b/src/codegen/function.ts new file mode 100644 index 000000000..af52b3d1b --- /dev/null +++ b/src/codegen/function.ts @@ -0,0 +1,291 @@ +import { CompilerContext } from "../context"; +import { enabledInline } from "../config/features"; +import { getType, resolveTypeRef } from "../types/resolveDescriptors"; +import { ops, funcIdOf } from "./util"; +import { TypeDescription, FunctionDescription, TypeRef } from "../types/types"; +import { + FuncAstFunction, + FuncAstStmt, + FuncAstFormalFunctionParam, + FuncAstFunctionAttribute, + FuncAstExpr, + FuncType, + FuncTensorType, + UNIT_TYPE, +} from "../func/syntax"; +import { StatementGen } from "./statement"; +import { resolveFuncTypeUnpack } from "./type"; + +/** + * Encapsulates generation of Func functions from the Tact function. + */ +export class FunctionGen { + /** + * @param tactFun Type description of the Tact function. + */ + private constructor( + private ctx: CompilerContext, + private tactFun: FunctionDescription, + ) {} + + static fromTact( + ctx: CompilerContext, + tactFun: FunctionDescription, + ): FunctionGen { + return new FunctionGen(ctx, tactFun); + } + + /** + * Generates Func types based on the Tact type definition. + * TODO: Why do they use a separate function for this. + */ + private resolveFuncType( + descriptor: TypeRef | TypeDescription | string, + optional: boolean = false, + usePartialFields: boolean = false, + ): FuncType { + // string + if (typeof descriptor === "string") { + return this.resolveFuncType( + getType(this.ctx, descriptor), + false, + usePartialFields, + ); + } + + // TypeRef + if (descriptor.kind === "ref") { + return this.resolveFuncType( + getType(this.ctx, descriptor.name), + descriptor.optional, + usePartialFields, + ); + } + if (descriptor.kind === "map") { + return { kind: "cell" }; + } + if (descriptor.kind === "ref_bounced") { + return this.resolveFuncType( + getType(this.ctx, descriptor.name), + false, + true, + ); + } + if (descriptor.kind === "void") { + return UNIT_TYPE; + } + + // TypeDescription + if (descriptor.kind === "primitive_type_decl") { + if (descriptor.name === "Int") { + return { kind: "int" }; + } else if (descriptor.name === "Bool") { + return { kind: "int" }; + } else if (descriptor.name === "Slice") { + return { kind: "slice" }; + } else if (descriptor.name === "Cell") { + return { kind: "cell" }; + } else if (descriptor.name === "Builder") { + return { kind: "builder" }; + } else if (descriptor.name === "Address") { + return { kind: "slice" }; + } else if (descriptor.name === "String") { + return { kind: "slice" }; + } else if (descriptor.name === "StringBuilder") { + return { kind: "tuple" }; + } else { + throw Error(`Unknown primitive type: ${descriptor.name}`); + } + } else if (descriptor.kind === "struct") { + const fieldsToUse = usePartialFields + ? descriptor.fields.slice(0, descriptor.partialFieldCount) + : descriptor.fields; + if (optional || fieldsToUse.length === 0) { + return { kind: "tuple" }; + } else { + const value = fieldsToUse.map((v) => + this.resolveFuncType(v.type, false, usePartialFields), + ) as FuncTensorType; + return { kind: "tensor", value }; + } + } else if (descriptor.kind === "contract") { + if (optional || descriptor.fields.length === 0) { + return { kind: "tuple" }; + } else { + const value = descriptor.fields.map((v) => + this.resolveFuncType(v.type, false, usePartialFields), + ) as FuncTensorType; + return { kind: "tensor", value }; + } + } + + // Unreachable + throw Error(`Unknown type: ${descriptor.kind}`); + } + + private resolveFuncPrimitive( + descriptor: TypeRef | TypeDescription | string, + ): boolean { + // String + if (typeof descriptor === "string") { + return this.resolveFuncPrimitive(getType(this.ctx, descriptor)); + } + + // TypeRef + if (descriptor.kind === "ref") { + return this.resolveFuncPrimitive( + getType(this.ctx, descriptor.name), + ); + } + if (descriptor.kind === "map") { + return true; + } + if (descriptor.kind === "ref_bounced") { + throw Error("Unimplemented: ref_bounced descriptor"); + } + if (descriptor.kind === "void") { + return true; + } + + // TypeDescription + if (descriptor.kind === "primitive_type_decl") { + if (descriptor.name === "Int") { + return true; + } else if (descriptor.name === "Bool") { + return true; + } else if (descriptor.name === "Slice") { + return true; + } else if (descriptor.name === "Cell") { + return true; + } else if (descriptor.name === "Builder") { + return true; + } else if (descriptor.name === "Address") { + return true; + } else if (descriptor.name === "String") { + return true; + } else if (descriptor.name === "StringBuilder") { + return true; + } else { + throw Error(`Unknown primitive type: ${descriptor.name}`); + } + } else if (descriptor.kind === "struct") { + return false; + } else if (descriptor.kind === "contract") { + return false; + } + + // Unreachable + throw Error(`Unknown type: ${descriptor.kind}`); + } + + // NOTE: writeFunction + /** + * Generates Func function from the Tact funciton description. + */ + public generate(): FuncAstFunction { + if (this.tactFun.ast.kind !== "function_def") { + throw new Error(`Unknown function kind: ${this.tactFun.ast.kind}`); + } + + let returnTy = this.resolveFuncType(this.tactFun.returns); + // let returnsStr: string | null; + const self: TypeDescription | undefined = this.tactFun.self + ? getType(this.ctx, this.tactFun.self) + : undefined; + if (self !== undefined && this.tactFun.isMutating) { + // Add `self` to the method signature as it is mutating in the body. + const selfTy = this.resolveFuncType(self); + returnTy = { kind: "tensor", value: [selfTy, returnTy] }; + // returnsStr = resolveFuncTypeUnpack(ctx, self, funcIdOf("self")); + } + + const params: FuncAstFormalFunctionParam[] = this.tactFun.params.reduce( + (acc, a) => [ + ...acc, + { + kind: "function_param", + ty: this.resolveFuncType(a.type), + name: funcIdOf(a.name), + }, + ], + self + ? [ + { + kind: "function_param", + ty: this.resolveFuncType(self), + name: funcIdOf("self"), + }, + ] + : [], + ); + + // TODO: handle native functions delcs. should be in a separatre funciton + + const name = self + ? ops.extension(self.name, this.tactFun.name) + : ops.global(this.tactFun.name); + + // Prepare function attributes + let attrs: FuncAstFunctionAttribute[] = ["impure"]; + if (enabledInline(this.ctx) || this.tactFun.isInline) { + attrs.push("inline"); + } + // TODO: handle stdlib + // if (f.origin === "stdlib") { + // ctx.context("stdlib"); + // } + + // Write function body + const body: FuncAstStmt[] = []; + + // Add arguments + if (self) { + const varName = resolveFuncTypeUnpack( + this.ctx, + self, + funcIdOf("self"), + ); + const init: FuncAstExpr = { + kind: "id_expr", + value: funcIdOf("self"), + }; + body.push({ + kind: "var_def_stmt", + name: varName, + init, + ty: undefined, + }); + } + for (const a of this.tactFun.ast.params) { + if (!this.resolveFuncPrimitive(resolveTypeRef(this.ctx, a.type))) { + const name = resolveFuncTypeUnpack( + this.ctx, + resolveTypeRef(this.ctx, a.type), + funcIdOf(a.name), + ); + const init: FuncAstExpr = { + kind: "id_expr", + value: funcIdOf(a.name), + }; + body.push({ kind: "var_def_stmt", name, init, ty: undefined }); + } + } + + const selfName = + self !== undefined + ? resolveFuncTypeUnpack(this.ctx, self, funcIdOf("self")) + : undefined; + // Process statements + this.tactFun.ast.statements.forEach((stmt) => { + const funcStmt = StatementGen.fromTact( + this.ctx, + stmt, + selfName, + this.tactFun.returns, + ).writeStatement(); + body.push(funcStmt); + }); + + return { kind: "function", attrs, params, returnTy, body }; + } +} diff --git a/src/codegen/index.ts b/src/codegen/index.ts new file mode 100644 index 000000000..cdc3b3db3 --- /dev/null +++ b/src/codegen/index.ts @@ -0,0 +1,4 @@ +export { ContractGen } from "./contract"; +export { FunctionGen } from "./function"; +export { StatementGen } from "./statement"; +export { ExpressionGen } from "./expression"; diff --git a/src/codegen/statement.ts b/src/codegen/statement.ts new file mode 100644 index 000000000..d03aefda8 --- /dev/null +++ b/src/codegen/statement.ts @@ -0,0 +1,498 @@ +import { CompilerContext } from "../context"; +import { funcIdOf } from "./util"; +import { getType, resolveTypeRef } from "../types/resolveDescriptors"; +import { getExpType } from "../types/resolveExpression"; +import { TypeRef } from "../types/types"; +import { + AstCondition, + AstStatement, + isWildcard, +} from "../grammar/ast"; +import { ExpressionGen } from "./expression"; +import { resolveFuncTypeUnpack, resolveFuncType } from "./type"; +import { + FuncAstStmt, + FuncAstConditionStmt, + FuncAstExpr, + FuncAstTupleExpr, + FuncAstUnitExpr, +} from "../func/syntax"; + +/** + * Encapsulates generation of Func statements from the Tact statement. + */ +export class StatementGen { + /** + * @param tactStmt Tact AST statement + * @param selfName Actual name of the `self` parameter present in the Func code. + * @param returns The return value of the return statement. + */ + private constructor( + private ctx: CompilerContext, + private tactStmt: AstStatement, + private selfName?: string, + private returns?: TypeRef, + ) {} + + static fromTact( + ctx: CompilerContext, + tactStmt: AstStatement, + selfVarName?: string, + returns?: TypeRef, + ): StatementGen { + return new StatementGen(ctx, tactStmt, selfVarName, returns); + } + + /** + * Tranforms the Tact conditional statement to the Func one. + */ + private writeCondition( + f: AstCondition, + ): FuncAstConditionStmt { + const writeStmt = (stmt: AstStatement) => + StatementGen.fromTact( + this.ctx, + stmt, + this.selfName, + this.returns, + ).writeStatement(); + const condition = ExpressionGen.fromTact( + this.ctx, + f.condition, + ).writeExpression(); + const thenBlock = f.trueStatements.map(writeStmt); + const elseStmt: FuncAstConditionStmt | undefined = + f.falseStatements !== null && f.falseStatements.length > 0 + ? { + kind: "condition_stmt", + condition: undefined, + ifnot: false, + body: f.falseStatements.map(writeStmt), + else: undefined, + } + : f.elseif + ? this.writeCondition(f.elseif) + : undefined; + return { + kind: "condition_stmt", + condition, + ifnot: false, + body: thenBlock, + else: elseStmt, + }; + } + + public writeStatement(): FuncAstStmt { + switch (this.tactStmt.kind) { + case "statement_return": { + const kind = "return_stmt"; + const selfVar = this.selfName + ? { kind: "id", value: this.selfName } + : undefined; + const getValue = (expr: FuncAstExpr): FuncAstExpr => + this.selfName + ? ({ + kind: "tuple_expr", + values: [selfVar!, expr], + } as FuncAstTupleExpr) + : expr; + if (this.tactStmt.expression) { + const castedReturns = ExpressionGen.fromTact( + this.ctx, + this.tactStmt.expression, + ).writeCastedExpression(this.returns!); + return { kind, value: getValue(castedReturns) }; + } else { + const unit = { kind: "unit_expr" } as FuncAstUnitExpr; + return { kind, value: getValue(unit) }; + } + } + case "statement_let": { + // Underscore name case + if (isWildcard(this.tactStmt.name)) { + const expr = ExpressionGen.fromTact( + this.ctx, + this.tactStmt.expression, + ).writeExpression(); + return { kind: "expr_stmt", expr }; + } + + // Contract/struct case + const t = + this.tactStmt.type === null + ? getExpType(this.ctx, this.tactStmt.expression) + : resolveTypeRef(this.ctx, this.tactStmt.type); + + if (t.kind === "ref") { + const tt = getType(this.ctx, t.name); + if (tt.kind === "contract" || tt.kind === "struct") { + if (t.optional) { + const name = funcIdOf(this.tactStmt.name); + const init = ExpressionGen.fromTact( + this.ctx, + this.tactStmt.expression, + ).writeCastedExpression(t); + return { + kind: "var_def_stmt", + name, + ty: { kind: "tuple" }, + init, + }; + } else { + const name = resolveFuncTypeUnpack( + this.ctx, + t, + funcIdOf(this.tactStmt.name), + ); + const init = ExpressionGen.fromTact( + this.ctx, + this.tactStmt.expression, + ).writeCastedExpression(t); + return { + kind: "var_def_stmt", + name, + ty: undefined, + init, + }; + } + } + } + + const ty = resolveFuncType(this.ctx, t); + const name = funcIdOf(this.tactStmt.name); + const init = ExpressionGen.fromTact( + this.ctx, + this.tactStmt.expression, + ).writeCastedExpression(t); + return { kind: "var_def_stmt", name, ty, init }; + } + + // case "statement_assign": { + // // Prepare lvalue + // const lvaluePath = tryExtractPath(f.path); + // if (lvaluePath === null) { + // // typechecker is supposed to catch this + // throwInternalCompilerError( + // `Assignments are allowed only into path expressions, i.e. identifiers, or sequences of direct contract/struct/message accesses, like "self.foo" or "self.structure.field"`, + // f.path.loc, + // ); + // } + // const path = writePathExpression(lvaluePath); + // + // // Contract/struct case + // const t = getExpType(ctx.ctx, f.path); + // if (t.kind === "ref") { + // const tt = getType(ctx.ctx, t.name); + // if (tt.kind === "contract" || tt.kind === "struct") { + // ctx.append( + // `${resolveFuncTypeUnpack(t, path, ctx)} = ${writeCastedExpression(f.expression, t, ctx)};`, + // ); + // return; + // } + // } + // + // ctx.append( + // `${path} = ${writeCastedExpression(f.expression, t, ctx)};`, + // ); + // return; + // } + // case "statement_augmentedassign": { + // const lvaluePath = tryExtractPath(f.path); + // if (lvaluePath === null) { + // // typechecker is supposed to catch this + // throwInternalCompilerError( + // `Assignments are allowed only into path expressions, i.e. identifiers, or sequences of direct contract/struct/message accesses, like "self.foo" or "self.structure.field"`, + // f.path.loc, + // ); + // } + // const path = writePathExpression(lvaluePath); + // const t = getExpType(ctx.ctx, f.path); + // ctx.append( + // `${path} = ${cast(t, t, `${path} ${f.op} ${writeExpression(f.expression, ctx)}`, ctx)};`, + // ); + // return; + // } + case "statement_condition": { + return this.writeCondition(this.tactStmt); + } + case "statement_expression": { + const expr = ExpressionGen.fromTact( + this.ctx, + this.tactStmt.expression, + ).writeExpression(); + return { kind: "expr_stmt", expr }; + } + // case "statement_while": { + // ctx.append(`while (${writeExpression(f.condition, ctx)}) {`); + // ctx.inIndent(() => { + // for (const s of f.statements) { + // writeStatement(s, self, returns, ctx); + // } + // }); + // ctx.append(`}`); + // return; + // } + // case "statement_until": { + // ctx.append(`do {`); + // ctx.inIndent(() => { + // for (const s of f.statements) { + // writeStatement(s, self, returns, ctx); + // } + // }); + // ctx.append(`} until (${writeExpression(f.condition, ctx)});`); + // return; + // } + // case "statement_repeat": { + // ctx.append(`repeat (${writeExpression(f.iterations, ctx)}) {`); + // ctx.inIndent(() => { + // for (const s of f.statements) { + // writeStatement(s, self, returns, ctx); + // } + // }); + // ctx.append(`}`); + // return; + // } + // case "statement_try": { + // ctx.append(`try {`); + // ctx.inIndent(() => { + // for (const s of f.statements) { + // writeStatement(s, self, returns, ctx); + // } + // }); + // ctx.append("} catch (_) { }"); + // return; + // } + // case "statement_try_catch": { + // ctx.append(`try {`); + // ctx.inIndent(() => { + // for (const s of f.statements) { + // writeStatement(s, self, returns, ctx); + // } + // }); + // if (isWildcard(f.catchName)) { + // ctx.append(`} catch (_) {`); + // } else { + // ctx.append(`} catch (_, ${funcIdOf(f.catchName)}) {`); + // } + // ctx.inIndent(() => { + // for (const s of f.catchStatements) { + // writeStatement(s, self, returns, ctx); + // } + // }); + // ctx.append(`}`); + // return; + // } + // case "statement_foreach": { + // const mapPath = tryExtractPath(f.map); + // if (mapPath === null) { + // // typechecker is supposed to catch this + // throwInternalCompilerError( + // `foreach is only allowed over maps that are path expressions, i.e. identifiers, or sequences of direct contract/struct/message accesses, like "self.foo" or "self.structure.field"`, + // f.map.loc, + // ); + // } + // const path = writePathExpression(mapPath); + // + // const t = getExpType(ctx.ctx, f.map); + // if (t.kind !== "map") { + // throw Error("Unknown map type"); + // } + // + // const flag = freshIdentifier("flag"); + // const key = isWildcard(f.keyName) + // ? freshIdentifier("underscore") + // : funcIdOf(f.keyName); + // const value = isWildcard(f.valueName) + // ? freshIdentifier("underscore") + // : funcIdOf(f.valueName); + // + // // Handle Int key + // if (t.key === "Int") { + // let bits = 257; + // let kind = "int"; + // if (t.keyAs?.startsWith("int")) { + // bits = parseInt(t.keyAs.slice(3), 10); + // } else if (t.keyAs?.startsWith("uint")) { + // bits = parseInt(t.keyAs.slice(4), 10); + // kind = "uint"; + // } + // if (t.value === "Int") { + // let vBits = 257; + // let vKind = "int"; + // if (t.valueAs?.startsWith("int")) { + // vBits = parseInt(t.valueAs.slice(3), 10); + // } else if (t.valueAs?.startsWith("uint")) { + // vBits = parseInt(t.valueAs.slice(4), 10); + // vKind = "uint"; + // } + // + // ctx.append( + // `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_${vKind}`)}(${path}, ${bits}, ${vBits});`, + // ); + // ctx.append(`while (${flag}) {`); + // ctx.inIndent(() => { + // for (const s of f.statements) { + // writeStatement(s, self, returns, ctx); + // } + // ctx.append( + // `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_${vKind}`)}(${path}, ${bits}, ${key}, ${vBits});`, + // ); + // }); + // ctx.append(`}`); + // } else if (t.value === "Bool") { + // ctx.append( + // `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_int`)}(${path}, ${bits}, 1);`, + // ); + // ctx.append(`while (${flag}) {`); + // ctx.inIndent(() => { + // for (const s of f.statements) { + // writeStatement(s, self, returns, ctx); + // } + // ctx.append( + // `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_int`)}(${path}, ${bits}, ${key}, 1);`, + // ); + // }); + // ctx.append(`}`); + // } else if (t.value === "Cell") { + // ctx.append( + // `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_cell`)}(${path}, ${bits});`, + // ); + // ctx.append(`while (${flag}) {`); + // ctx.inIndent(() => { + // for (const s of f.statements) { + // writeStatement(s, self, returns, ctx); + // } + // ctx.append( + // `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_cell`)}(${path}, ${bits}, ${key});`, + // ); + // }); + // ctx.append(`}`); + // } else if (t.value === "Address") { + // ctx.append( + // `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_slice`)}(${path}, ${bits});`, + // ); + // ctx.append(`while (${flag}) {`); + // ctx.inIndent(() => { + // for (const s of f.statements) { + // writeStatement(s, self, returns, ctx); + // } + // ctx.append( + // `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_slice`)}(${path}, ${bits}, ${key});`, + // ); + // }); + // ctx.append(`}`); + // } else { + // // value is struct + // ctx.append( + // `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_${kind}_cell`)}(${path}, ${bits});`, + // ); + // ctx.append(`while (${flag}) {`); + // ctx.inIndent(() => { + // ctx.append( + // `var ${resolveFuncTypeUnpack(t.value, funcIdOf(f.valueName), ctx)} = ${ops.typeNotNull(t.value, ctx)}(${ops.readerOpt(t.value, ctx)}(${value}));`, + // ); + // for (const s of f.statements) { + // writeStatement(s, self, returns, ctx); + // } + // ctx.append( + // `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_${kind}_cell`)}(${path}, ${bits}, ${key});`, + // ); + // }); + // ctx.append(`}`); + // } + // } + // + // // Handle address key + // if (t.key === "Address") { + // if (t.value === "Int") { + // let vBits = 257; + // let vKind = "int"; + // if (t.valueAs?.startsWith("int")) { + // vBits = parseInt(t.valueAs.slice(3), 10); + // } else if (t.valueAs?.startsWith("uint")) { + // vBits = parseInt(t.valueAs.slice(4), 10); + // vKind = "uint"; + // } + // ctx.append( + // `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_${vKind}`)}(${path}, 267, ${vBits});`, + // ); + // ctx.append(`while (${flag}) {`); + // ctx.inIndent(() => { + // for (const s of f.statements) { + // writeStatement(s, self, returns, ctx); + // } + // ctx.append( + // `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_${vKind}`)}(${path}, 267, ${key}, ${vBits});`, + // ); + // }); + // ctx.append(`}`); + // } else if (t.value === "Bool") { + // ctx.append( + // `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_int`)}(${path}, 267, 1);`, + // ); + // ctx.append(`while (${flag}) {`); + // ctx.inIndent(() => { + // for (const s of f.statements) { + // writeStatement(s, self, returns, ctx); + // } + // ctx.append( + // `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_int`)}(${path}, 267, ${key}, 1);`, + // ); + // }); + // ctx.append(`}`); + // } else if (t.value === "Cell") { + // ctx.append( + // `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_cell`)}(${path}, 267);`, + // ); + // ctx.append(`while (${flag}) {`); + // ctx.inIndent(() => { + // for (const s of f.statements) { + // writeStatement(s, self, returns, ctx); + // } + // ctx.append( + // `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_cell`)}(${path}, 267, ${key});`, + // ); + // }); + // ctx.append(`}`); + // } else if (t.value === "Address") { + // ctx.append( + // `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_slice`)}(${path}, 267);`, + // ); + // ctx.append(`while (${flag}) {`); + // ctx.inIndent(() => { + // for (const s of f.statements) { + // writeStatement(s, self, returns, ctx); + // } + // ctx.append( + // `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_slice`)}(${path}, 267, ${key});`, + // ); + // }); + // ctx.append(`}`); + // } else { + // // value is struct + // ctx.append( + // `var (${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_min_slice_cell`)}(${path}, 267);`, + // ); + // ctx.append(`while (${flag}) {`); + // ctx.inIndent(() => { + // ctx.append( + // `var ${resolveFuncTypeUnpack(t.value, funcIdOf(f.valueName), ctx)} = ${ops.typeNotNull(t.value, ctx)}(${ops.readerOpt(t.value, ctx)}(${value}));`, + // ); + // for (const s of f.statements) { + // writeStatement(s, self, returns, ctx); + // } + // ctx.append( + // `(${key}, ${value}, ${flag}) = ${ctx.used(`__tact_dict_next_slice_cell`)}(${path}, 267, ${key});`, + // ); + // }); + // ctx.append(`}`); + // } + // } + // + // return; + // } + } + + throw Error(`Unknown statement kind: ${this.tactStmt.kind}`); + } +} diff --git a/src/codegen/type.ts b/src/codegen/type.ts new file mode 100644 index 000000000..74151719f --- /dev/null +++ b/src/codegen/type.ts @@ -0,0 +1,194 @@ +import { CompilerContext } from "../context"; +import { TypeDescription, TypeRef } from "../types/types"; +import { getType } from "../types/resolveDescriptors"; +import { FuncType, UNIT_TYPE } from "../func/syntax"; + +/** + * Unpacks string representation of a user-defined Tact type from its type description. + * The generated string represents an identifier avialable in the current scope. + */ +export function resolveFuncTypeUnpack( + ctx: CompilerContext, + descriptor: TypeRef | TypeDescription | string, + name: string, + optional: boolean = false, + usePartialFields: boolean = false, +): string { + // String + if (typeof descriptor === "string") { + return resolveFuncTypeUnpack( + ctx, + getType(ctx, descriptor), + name, + false, + usePartialFields, + ); + } + + // TypeRef + if (descriptor.kind === "ref") { + return resolveFuncTypeUnpack( + ctx, + getType(ctx, descriptor.name), + name, + descriptor.optional, + usePartialFields, + ); + } + if (descriptor.kind === "map") { + return name; + } + if (descriptor.kind === "ref_bounced") { + return resolveFuncTypeUnpack( + ctx, + getType(ctx, descriptor.name), + name, + false, + true, + ); + } + if (descriptor.kind === "void") { + throw Error(`Void type is not allowed in function arguments: ${name}`); + } + + // TypeDescription + if (descriptor.kind === "primitive_type_decl") { + return name; + } else if (descriptor.kind === "struct") { + const fieldsToUse = usePartialFields + ? descriptor.fields.slice(0, descriptor.partialFieldCount) + : descriptor.fields; + if (optional || fieldsToUse.length === 0) { + return name; + } else { + return ( + "(" + + fieldsToUse + .map((v) => + resolveFuncTypeUnpack( + ctx, + v.type, + name + `'` + v.name, + false, + usePartialFields, + ), + ) + .join(", ") + + ")" + ); + } + } else if (descriptor.kind === "contract") { + if (optional || descriptor.fields.length === 0) { + return name; + } else { + return ( + "(" + + descriptor.fields + .map((v) => + resolveFuncTypeUnpack( + ctx, + v.type, + name + `'` + v.name, + false, + usePartialFields, + ), + ) + .join(", ") + + ")" + ); + } + } + + // Unreachable + throw Error(`Unknown type: ${descriptor.kind}`); +} + +/** + * Generates Func type from the Tact type. + */ +export function resolveFuncType( + ctx: CompilerContext, + descriptor: TypeRef | TypeDescription | string, + optional: boolean = false, + usePartialFields: boolean = false, +): FuncType { + // String + if (typeof descriptor === "string") { + return resolveFuncType( + ctx, + getType(ctx, descriptor), + false, + usePartialFields, + ); + } + + // TypeRef + if (descriptor.kind === "ref") { + return resolveFuncType( + ctx, + getType(ctx, descriptor.name), + descriptor.optional, + usePartialFields, + ); + } + if (descriptor.kind === "map") { + return { kind: "cell" }; + } + if (descriptor.kind === "ref_bounced") { + return resolveFuncType(ctx, getType(ctx, descriptor.name), false, true); + } + if (descriptor.kind === "void") { + return UNIT_TYPE; + } + + // TypeDescription + if (descriptor.kind === "primitive_type_decl") { + if (descriptor.name === "Int") { + return { kind: "int" }; + } else if (descriptor.name === "Bool") { + return { kind: "int" }; + } else if (descriptor.name === "Slice") { + return { kind: "slice" }; + } else if (descriptor.name === "Cell") { + return { kind: "cell" }; + } else if (descriptor.name === "Builder") { + return { kind: "builder" }; + } else if (descriptor.name === "Address") { + return { kind: "slice" }; + } else if (descriptor.name === "String") { + return { kind: "slice" }; + } else if (descriptor.name === "StringBuilder") { + return { kind: "tuple" }; + } else { + throw Error(`Unknown primitive type: ${descriptor.name}`); + } + } else if (descriptor.kind === "struct") { + const fieldsToUse = usePartialFields + ? descriptor.fields.slice(0, descriptor.partialFieldCount) + : descriptor.fields; + if (optional || fieldsToUse.length === 0) { + return { kind: "tuple" }; + } else { + return { + kind: "tensor", + value: fieldsToUse.map((v) => + resolveFuncType(ctx, v.type, false, usePartialFields), + ), + }; + } + } else if (descriptor.kind === "contract") { + if (optional || descriptor.fields.length === 0) { + return { kind: "tuple" }; + } else { + return { + kind: "tensor", + value: descriptor.fields.map((v) => + resolveFuncType(ctx, v.type, false, usePartialFields), + ), + }; + } + } + + // Unreachable + throw Error(`Unknown type: ${descriptor.kind}`); +} diff --git a/src/codegen/util.ts b/src/codegen/util.ts new file mode 100644 index 000000000..053bfa011 --- /dev/null +++ b/src/codegen/util.ts @@ -0,0 +1,57 @@ +import { getType } from "../types/resolveDescriptors"; +import { CompilerContext } from "../context"; +import { TypeRef } from "../types/types"; +import { FuncAstExpr, FuncAstIdExpr } from "../func/syntax"; +import { AstId, idText } from "../grammar/ast"; + +export namespace ops { + export function extension(type: string, name: string): string { + return `$${type}$_fun_${name}`; + } + export function global(name: string): string { + return `$global_${name}`; + } + export function typeAsOptional(type: string) { + return `$${type}$_as_optional`; + } + export function typeTensorCast(type: string) { + return `$${type}$_tensor_cast`; + } + export function nonModifying(name: string) { + return `${name}$not_mut`; + } +} + +/** + * Wraps the expression in `_as_optional()` if needed. + */ +export function cast( + ctx: CompilerContext, + from: TypeRef, + to: TypeRef, + expr: FuncAstExpr, +): FuncAstExpr { + if (from.kind === "ref" && to.kind === "ref") { + if (from.name !== to.name) { + throw Error(`Impossible: ${from.name} != ${to.name}`); + } + if (!from.optional && to.optional) { + const type = getType(ctx, from.name); + if (type.kind === "struct") { + const fun = { + kind: "id_expr", + value: ops.typeAsOptional(type.name), + } as FuncAstIdExpr; + return { kind: "call_expr", fun, args: [expr] }; + } + } + } + return expr; +} + +export function funcIdOf(ident: AstId | string): string { + return typeof ident === "string" ? `$${ident}` : `$${idText(ident)}`; +} +export function funcInitIdOf(ident: AstId | string): string { + return typeof ident === "string" ? `$${ident}` : `$init`; +} diff --git a/src/func/formatter.ts b/src/func/formatter.ts new file mode 100644 index 000000000..eea5ecdbc --- /dev/null +++ b/src/func/formatter.ts @@ -0,0 +1,322 @@ +import { + FuncAstNode, + FuncType, + FuncAstIdExpr, + FuncAstPragma, + FuncAstComment, + FuncAstInclude, + FuncAstModule, + FuncAstFunction, + FuncAstVarDefStmt, + FuncAstReturnStmt, + FuncAstBlockStmt, + FuncAstRepeatStmt, + FuncAstConditionStmt, + FuncAstDoUntilStmt, + FuncAstWhileStmt, + FuncAstExprStmt, + FuncAstTryCatchStmt, + FuncAstConstant, + FuncAstGlobalVariable, + FuncAstCallExpr, + FuncAstAugmentedAssignExpr, + FuncAstTernaryExpr, + FuncAstBinaryExpr, + FuncAstUnaryExpr, + FuncAstNumberExpr, + FuncAstBoolExpr, + FuncAstStringExpr, + FuncAstNilExpr, + FuncAstApplyExpr, + FuncAstTupleExpr, + FuncAstTensorExpr, + FuncAstUnitExpr, + FuncAstHoleExpr, + FuncAstPrimitiveTypeExpr, +} from "./syntax"; + +/** + * Provides utilities to print the generated Func AST. + */ +export class FuncFormatter { + public static dump(node: FuncAstNode): string { + switch (node.kind) { + case "id_expr": + return this.formatIdExpr(node as FuncAstIdExpr); + case "include": + return this.formatInclude(node as FuncAstInclude); + case "pragma": + return this.formatPragma(node as FuncAstPragma); + case "comment": + return this.formatComment(node as FuncAstComment); + case "int": + case "cell": + case "slice": + case "builder": + case "cont": + case "tuple": + case "tensor": + case "type": + return this.formatType(node as FuncType); + case "module": + return this.formatModule(node as FuncAstModule); + case "function": + return this.formatFunction(node as FuncAstFunction); + case "var_def_stmt": + return this.formatVarDefStmt(node as FuncAstVarDefStmt); + case "return_stmt": + return this.formatReturnStmt(node as FuncAstReturnStmt); + case "block_stmt": + return this.formatBlockStmt(node as FuncAstBlockStmt); + case "repeat_stmt": + return this.formatRepeatStmt(node as FuncAstRepeatStmt); + case "condition_stmt": + return this.formatConditionStmt(node as FuncAstConditionStmt); + case "do_until_stmt": + return this.formatDoUntilStmt(node as FuncAstDoUntilStmt); + case "while_stmt": + return this.formatWhileStmt(node as FuncAstWhileStmt); + case "expr_stmt": + return this.formatExprStmt(node as FuncAstExprStmt); + case "try_catch_stmt": + return this.formatTryCatchStmt(node as FuncAstTryCatchStmt); + case "constant": + return this.formatConstant(node as FuncAstConstant); + case "global_variable": + return this.formatGlobalVariable(node as FuncAstGlobalVariable); + case "call_expr": + return this.formatCallExpr(node as FuncAstCallExpr); + case "augmented_assign_expr": + return this.formatAugmentedAssignExpr( + node as FuncAstAugmentedAssignExpr, + ); + case "ternary_expr": + return this.formatTernaryExpr(node as FuncAstTernaryExpr); + case "binary_expr": + return this.formatBinaryExpr(node as FuncAstBinaryExpr); + case "unary_expr": + return this.formatUnaryExpr(node as FuncAstUnaryExpr); + case "number_expr": + return this.formatNumberExpr(node as FuncAstNumberExpr); + case "bool_expr": + return this.formatBoolExpr(node as FuncAstBoolExpr); + case "string_expr": + return this.formatStringExpr(node as FuncAstStringExpr); + case "nil_expr": + return this.formatNilExpr(node as FuncAstNilExpr); + case "apply_expr": + return this.formatApplyExpr(node as FuncAstApplyExpr); + case "tuple_expr": + return this.formatTupleExpr(node as FuncAstTupleExpr); + case "tensor_expr": + return this.formatTensorExpr(node as FuncAstTensorExpr); + case "unit_expr": + return this.formatUnitExpr(node as FuncAstUnitExpr); + case "hole_expr": + return this.formatHoleExpr(node as FuncAstHoleExpr); + case "primitive_type_expr": + return this.formatPrimitiveTypeExpr( + node as FuncAstPrimitiveTypeExpr, + ); + default: + throw new Error(`Unsupported node kind: ${node}`); + } + } + + private static formatModule(node: FuncAstModule): string { + return node.entries.map((entry) => this.dump(entry)).join("\n"); + } + + private static formatFunction(node: FuncAstFunction): string { + const attrs = node.attrs.join(" "); + const params = node.params + .map((param) => `${param.ty} ${param.name}`) + .join(", "); + const returnType = node.returnTy; + const body = node.body.map((stmt) => this.dump(stmt)).join("\n"); + return `${attrs} ${params} -> ${returnType} {\n${body}\n}`; + } + + private static formatVarDefStmt(node: FuncAstVarDefStmt): string { + const type = node.ty ? `${this.dump(node.ty)} ` : ""; + const init = node.init ? ` = ${this.dump(node.init)}` : ""; + return `var ${node.name}: ${type}${init};`; + } + + private static formatReturnStmt(node: FuncAstReturnStmt): string { + const value = node.value ? ` ${this.dump(node.value)}` : ""; + return `return${value};`; + } + + private static formatBlockStmt(node: FuncAstBlockStmt): string { + const body = node.body.map((stmt) => this.dump(stmt)).join("\n"); + return `{\n${body}\n}`; + } + + private static formatRepeatStmt(node: FuncAstRepeatStmt): string { + const condition = this.dump(node.condition); + const body = node.body.map((stmt) => this.dump(stmt)).join("\n"); + return `repeat ${condition} {\n${body}\n}`; + } + + private static formatConditionStmt(node: FuncAstConditionStmt): string { + const condition = node.condition ? this.dump(node.condition) : ""; + const ifnot = node.ifnot ? "ifnot" : "if"; + const thenBlock = node.body + .map((stmt) => this.dump(stmt)) + .join("\n"); + const elseBlock = node.else ? this.formatConditionStmt(node.else) : ""; + return `${ifnot} ${condition} {\n${thenBlock}\n}${elseBlock ? ` else {\n${elseBlock}\n}` : ""}`; + } + + private static formatDoUntilStmt(node: FuncAstDoUntilStmt): string { + const condition = this.dump(node.condition); + const body = node.body.map((stmt) => this.dump(stmt)).join("\n"); + return `do {\n${body}\n} until ${condition};`; + } + + private static formatWhileStmt(node: FuncAstWhileStmt): string { + const condition = this.dump(node.condition); + const body = node.body.map((stmt) => this.dump(stmt)).join("\n"); + return `while ${condition} {\n${body}\n}`; + } + + private static formatExprStmt(node: FuncAstExprStmt): string { + return `${this.dump(node.expr)};`; + } + + private static formatTryCatchStmt(node: FuncAstTryCatchStmt): string { + const tryBlock = node.tryBlock + .map((stmt) => this.dump(stmt)) + .join("\n"); + const catchBlock = node.catchBlock + .map((stmt) => this.dump(stmt)) + .join("\n"); + const catchVar = node.catchVar ? ` (${node.catchVar})` : ""; + return `try {\n${tryBlock}\n} catch${catchVar} {\n${catchBlock}\n}`; + } + + private static formatConstant(node: FuncAstConstant): string { + const type = this.dump(node.ty); + const init = this.dump(node.init); + return `const ${type} = ${init};`; + } + + private static formatGlobalVariable(node: FuncAstGlobalVariable): string { + const type = this.dump(node.ty); + return `global ${type} ${node.name};`; + } + + private static formatCallExpr(node: FuncAstCallExpr): string { + const fun = this.dump(node.fun); + const args = node.args.map((arg) => this.dump(arg)).join(", "); + return `${fun}(${args})`; + } + + private static formatAugmentedAssignExpr( + node: FuncAstAugmentedAssignExpr, + ): string { + const lhs = this.dump(node.lhs); + const rhs = this.dump(node.rhs); + return `${lhs} ${node.op} ${rhs}`; + } + + private static formatTernaryExpr(node: FuncAstTernaryExpr): string { + const cond = this.dump(node.cond); + const body = this.dump(node.body); + const elseExpr = this.dump(node.else); + return `${cond} ? ${body} : ${elseExpr}`; + } + + private static formatBinaryExpr(node: FuncAstBinaryExpr): string { + const lhs = this.dump(node.lhs); + const rhs = this.dump(node.rhs); + return `${lhs} ${node.op} ${rhs}`; + } + + private static formatUnaryExpr(node: FuncAstUnaryExpr): string { + const value = this.dump(node.value); + return `${node.op}${value}`; + } + + private static formatNumberExpr(node: FuncAstNumberExpr): string { + return node.value.toString(); + } + + private static formatBoolExpr(node: FuncAstBoolExpr): string { + return node.value.toString(); + } + + private static formatStringExpr(node: FuncAstStringExpr): string { + return `"${node.value}"`; + } + + private static formatNilExpr(_: FuncAstNilExpr): string { + return "nil"; + } + + private static formatApplyExpr(node: FuncAstApplyExpr): string { + const lhs = this.dump(node.lhs); + const rhs = this.dump(node.rhs); + return `${lhs} ${rhs}`; + } + + private static formatTupleExpr(node: FuncAstTupleExpr): string { + const values = node.values.map((value) => this.dump(value)).join(", "); + return `[${values}]`; + } + + private static formatTensorExpr(node: FuncAstTensorExpr): string { + const values = node.values.map((value) => this.dump(value)).join(", "); + return `(${values})`; + } + + private static formatUnitExpr(_: FuncAstUnitExpr): string { + return "()"; + } + + private static formatHoleExpr(node: FuncAstHoleExpr): string { + const id = node.id ? node.id : "_"; + const init = this.dump(node.init); + return `${id} = ${init}`; + } + + private static formatPrimitiveTypeExpr( + node: FuncAstPrimitiveTypeExpr, + ): string { + return node.ty.kind; + } + + private static formatIdExpr(node: FuncAstIdExpr): string { + return node.value; + } + + private static formatInclude(node: FuncAstInclude): string { + return `#include ${node.kind}`; + } + + private static formatPragma(node: FuncAstPragma): string { + return `#pragma ${node.kind}`; + } + + private static formatComment(node: FuncAstComment): string { + return `;; ${node.value}`; + } + + private static formatType(node: FuncType): string { + switch (node.kind) { + case "int": + case "cell": + case "slice": + case "builder": + case "cont": + case "tuple": + case "type": + return node.kind; + case "tensor": + return `tensor(${node.value.map((t) => this.formatType(t)).join(", ")})`; + default: + throw new Error(`Unsupported type kind: ${node}`); + } + } +} diff --git a/src/func/syntax.ts b/src/func/syntax.ts new file mode 100644 index 000000000..3a30ce3ff --- /dev/null +++ b/src/func/syntax.ts @@ -0,0 +1,343 @@ +/** + * The supported version of the Func compiler: + * https://github.com/ton-blockchain/ton/blob/6897b5624566a2ab9126596d8bc4980dfbcaff2d/crypto/func/func.h#L48 + */ +export const FUNC_VERSION: string = "0.4.4"; + +/** + * Represents an ordered collection of values. + * NOTE: Unit type `()` is a special case of the tensor type. + */ +export type FuncTensorType = FuncType[]; +export const UNIT_TYPE: FuncType = { + kind: "tensor", + value: [] as FuncTensorType, +}; + +/** + * Type annotations available within the syntax tree. + */ +export type FuncType = + | { kind: "int" } + | { kind: "cell" } + | { kind: "slice" } + | { kind: "builder" } + | { kind: "cont" } + | { kind: "tuple" } + | { kind: "tensor"; value: FuncTensorType } + | { kind: "type" }; + +export type FuncAstUnaryOp = "-" | "~"; + +export type FuncAstBinaryOp = + | "+" + | "-" + | "*" + | "/" + | "%" + | "=" + | "<" + | ">" + | "&" + | "|" + | "^" + | "==" + | "!=" + | "<=" + | ">=" + | "<=>" + | "<<" + | ">>" + | "~>>" + | "^>>" + | "~/" + | "^/" + | "~%" + | "^%" + | "/%"; + +export type FuncAstAugmentedAssignOp = + | "+=" + | "-=" + | "*=" + | "/=" + | "~/=" + | "^/=" + | "%=" + | "~%=" + | "^%=" + | "<<=" + | ">>=" + | "~>>=" + | "^>>=" + | "&=" + | "|=" + | "^="; + +export type FuncAstTmpVarClass = "In" | "Named" | "Tmp" | "UniqueName"; + +interface FuncAstVarDescrFlags { + Last: boolean; + Unused: boolean; + Const: boolean; + Int: boolean; + Zero: boolean; + NonZero: boolean; + Pos: boolean; + Neg: boolean; + Bool: boolean; + Bit: boolean; + Finite: boolean; + Nan: boolean; + Even: boolean; + Odd: boolean; + Null: boolean; + NotNull: boolean; +} + +export type FuncAstConstant = { + kind: "constant"; + ty: FuncType; + init: FuncAstExpr; +}; + +export type FuncAstIdExpr = { + kind: "id_expr"; + value: string; +}; + +export type FuncAstCallExpr = { + kind: "call_expr"; + fun: FuncAstExpr; + args: FuncAstExpr[]; +}; + +// Augmented assignment: a += 42; +export type FuncAstAugmentedAssignExpr = { + kind: "augmented_assign_expr"; + lhs: FuncAstExpr; + op: FuncAstAugmentedAssignOp; + rhs: FuncAstExpr; +}; + +export type FuncAstTernaryExpr = { + kind: "ternary_expr"; + cond: FuncAstExpr; + body: FuncAstExpr; + else: FuncAstExpr; +}; + +export type FuncAstBinaryExpr = { + kind: "binary_expr"; + lhs: FuncAstExpr; + op: FuncAstBinaryOp; + rhs: FuncAstExpr; +}; + +export type FuncAstUnaryExpr = { + kind: "unary_expr"; + op: FuncAstUnaryOp; + value: FuncAstExpr; +}; + +export type FuncAstNumberExpr = { + kind: "number_expr"; + value: bigint; +}; + +export type FuncAstBoolExpr = { + kind: "bool_expr"; + value: boolean; +}; + +export type FuncAstStringExpr = { + kind: "string_expr"; + value: string; +}; + +export type FuncAstNilExpr = { + kind: "nil_expr"; +}; + +export type FuncAstApplyExpr = { + kind: "apply_expr"; + lhs: FuncAstExpr; + rhs: FuncAstExpr; +}; + +export type FuncAstTupleExpr = { + kind: "tuple_expr"; + values: FuncAstExpr[]; +}; + +export type FuncAstTensorExpr = { + kind: "tensor_expr"; + values: FuncAstExpr[]; +}; + +export type FuncAstUnitExpr = { + kind: "unit_expr"; +}; + +// Defines a variable applying the local type inference rules: +// var x = 2; +// _ = 2; +export type FuncAstHoleExpr = { + kind: "hole_expr"; + id: string | undefined; + init: FuncAstExpr; +}; + +// Primitive types are used in the syntax tree to express polymorphism. +export type FuncAstPrimitiveTypeExpr = { + kind: "primitive_type_expr"; + ty: FuncType; +}; + +// Local variable definition: +// int x = 2; // ty = int +// var x = 2; // ty is undefined +export type FuncAstVarDefStmt = { + kind: "var_def_stmt"; + name: string; + ty: FuncType | undefined; + init: FuncAstExpr | undefined; +}; + +export type FuncAstReturnStmt = { + kind: "return_stmt"; + value: FuncAstExpr | undefined; +}; + +export type FuncAstBlockStmt = { + kind: "block_stmt"; + body: FuncAstStmt[]; +}; + +export type FuncAstRepeatStmt = { + kind: "repeat_stmt"; + condition: FuncAstExpr; + body: FuncAstStmt[]; +}; + +export type FuncAstConditionStmt = { + kind: "condition_stmt"; + condition?: FuncAstExpr; + ifnot: boolean; // negation: ifnot or elseifnot attribute + body: FuncAstStmt[]; + else?: FuncAstConditionStmt; +}; + +export type FuncAstDoUntilStmt = { + kind: "do_until_stmt"; + body: FuncAstStmt[]; + condition: FuncAstExpr; +}; + +export type FuncAstWhileStmt = { + kind: "while_stmt"; + condition: FuncAstExpr; + body: FuncAstStmt[]; +}; + +export type FuncAstExprStmt = { + kind: "expr_stmt"; + expr: FuncAstExpr; +}; + +export type FuncAstTryCatchStmt = { + kind: "try_catch_stmt"; + tryBlock: FuncAstStmt[]; + catchBlock: FuncAstStmt[]; + catchVar: string | null; +}; + +export type FuncAstFunctionAttribute = "impure" | "inline"; + +export type FuncAstFormalFunctionParam = { + kind: "function_param"; + name: string; + ty: FuncType; +}; + +export type FuncAstFunction = { + kind: "function"; + attrs: FuncAstFunctionAttribute[]; + params: FuncAstFormalFunctionParam[]; + returnTy: FuncType; + body: FuncAstStmt[]; +}; + +export type FuncAstComment = { + kind: "comment"; + value: string; +}; + +export type FuncAstInclude = { + kind: "include"; +}; + +export type FuncAstPragma = { + kind: "pragma"; +}; + +export type FuncAstGlobalVariable = { + kind: "global_variable"; + name: string; + ty: FuncType; +}; + +export type FuncAstModuleEntry = + | FuncAstInclude + | FuncAstPragma + | FuncAstFunction + | FuncAstComment + | FuncAstConstant + | FuncAstGlobalVariable; + +export type FuncAstModule = { + kind: "module"; + entries: FuncAstModuleEntry[]; +}; + +export type FuncAstLiteralExpr = + | FuncAstNumberExpr + | FuncAstBoolExpr + | FuncAstStringExpr + | FuncAstNilExpr; +export type FuncAstSimpleExpr = + | FuncAstIdExpr + | FuncAstTupleExpr + | FuncAstTensorExpr + | FuncAstUnitExpr + | FuncAstHoleExpr + | FuncAstPrimitiveTypeExpr; +export type FuncAstCompositeExpr = + | FuncAstCallExpr + | FuncAstAugmentedAssignExpr + | FuncAstTernaryExpr + | FuncAstBinaryExpr + | FuncAstUnaryExpr + | FuncAstApplyExpr; +export type FuncAstExpr = + | FuncAstLiteralExpr + | FuncAstSimpleExpr + | FuncAstCompositeExpr; + +export type FuncAstStmt = + | FuncAstBlockStmt + | FuncAstVarDefStmt + | FuncAstReturnStmt + | FuncAstRepeatStmt + | FuncAstConditionStmt + | FuncAstDoUntilStmt + | FuncAstWhileStmt + | FuncAstExprStmt + | FuncAstTryCatchStmt; + +export type FuncAstNode = + | FuncAstStmt + | FuncAstExpr + | FuncAstModule + | FuncAstModuleEntry + | FuncType; diff --git a/src/pipeline/build.ts b/src/pipeline/build.ts index bcc35e6a6..1aaecea23 100644 --- a/src/pipeline/build.ts +++ b/src/pipeline/build.ts @@ -116,7 +116,7 @@ export async function build(args: { const res = await compile( ctx, contract, - config.name + "_" + contract, + `${config.name}_${contract}`, ); for (const files of res.output.files) { const ffc = project.resolve(config.output, files.name); diff --git a/src/pipeline/compile.ts b/src/pipeline/compile.ts index 84fde8302..e73bd693b 100644 --- a/src/pipeline/compile.ts +++ b/src/pipeline/compile.ts @@ -1,14 +1,45 @@ import { CompilerContext } from "../context"; import { createABI } from "../generator/createABI"; import { writeProgram } from "../generator/writeProgram"; +import { ContractGen } from "../codegen"; +import { FuncFormatter } from "../func/formatter"; +export type CompilationOutput = { + entrypoint: string; + files: { + name: string; + code: string; + }[]; + abi: string; +}; + +export type CompilationResults = { + output: CompilationOutput; + ctx: CompilerContext; +}; + +/** + * Compiles the given contract to Func. + */ export async function compile( ctx: CompilerContext, - name: string, - basename: string, -) { - const abi = createABI(ctx, name); - const output = await writeProgram(ctx, abi, basename); - const cOutput = output; - return { output: cOutput, ctx }; + contractName: string, + abiName: string, +): Promise { + const abi = createABI(ctx, contractName); + if (process.env.NEW_CODEGEN === "1") { + const funcAst = ContractGen.fromTact( + ctx, + contractName, + abiName, + ).generate(); + const output = FuncFormatter.dump(funcAst); + throw new Error(`output:\n${output}`); + // return { output, ctx }; + } else { + const output = await writeProgram(ctx, abi, abiName); + console.log(`${contractName} output:`); + output.files.forEach((o) => console.log(`---------------\nname=${o.name}; code:\n${o.code}\n`)); + return { output, ctx }; + } }