Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP feat: new asm parser #1064

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/generator/writers/writeFunction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import { ops } from "./ops";
import { freshIdentifier } from "./freshIdentifier";
import { idTextErr, throwInternalCompilerError } from "../../errors";
import { ppAsmShuffle } from "../../prettyPrinter";
import { ppAsmShuffle, ppAsmExpressionsBlock } from "../../prettyPrinter";

export function writeCastedExpression(
expression: AstExpression,
Expand Down Expand Up @@ -581,7 +581,8 @@
};
ctx.asm(
ppAsmShuffle(asmShuffleEscaped),
fAst.instructions.join(" "),
ppAsmExpressionsBlock(fAst.expressions),

Check failure on line 584 in src/generator/writers/writeFunction.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, ubuntu-latest, npm)

Argument of type '<U>(ctx: Context<U>) => U' is not assignable to parameter of type 'string'.

Check failure on line 584 in src/generator/writers/writeFunction.ts

View workflow job for this annotation

GitHub Actions / test (22, ubuntu-latest)

Argument of type '<U>(ctx: Context<U>) => U' is not assignable to parameter of type 'string'.

Check failure on line 584 in src/generator/writers/writeFunction.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, ubuntu-latest, bun)

Argument of type '<U>(ctx: Context<U>) => U' is not assignable to parameter of type 'string'.

Check failure on line 584 in src/generator/writers/writeFunction.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, ubuntu-latest, yarn)

Argument of type '<U>(ctx: Context<U>) => U' is not assignable to parameter of type 'string'.

Check failure on line 584 in src/generator/writers/writeFunction.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, ubuntu-latest, pnpm)

Argument of type '<U>(ctx: Context<U>) => U' is not assignable to parameter of type 'string'.

Check failure on line 584 in src/generator/writers/writeFunction.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, macos-latest, bun)

Argument of type '<U>(ctx: Context<U>) => U' is not assignable to parameter of type 'string'.

Check failure on line 584 in src/generator/writers/writeFunction.ts

View workflow job for this annotation

GitHub Actions / test (22, macos-latest)

Argument of type '<U>(ctx: Context<U>) => U' is not assignable to parameter of type 'string'.

Check failure on line 584 in src/generator/writers/writeFunction.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, macos-latest, npm)

Argument of type '<U>(ctx: Context<U>) => U' is not assignable to parameter of type 'string'.

Check failure on line 584 in src/generator/writers/writeFunction.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, macos-latest, pnpm)

Argument of type '<U>(ctx: Context<U>) => U' is not assignable to parameter of type 'string'.

Check failure on line 584 in src/generator/writers/writeFunction.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, macos-latest, yarn)

Argument of type '<U>(ctx: Context<U>) => U' is not assignable to parameter of type 'string'.

Check failure on line 584 in src/generator/writers/writeFunction.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, windows-latest, yarn)

Argument of type '<U>(ctx: Context<U>) => U' is not assignable to parameter of type 'string'.

Check failure on line 584 in src/generator/writers/writeFunction.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, windows-latest, yarn)

Argument of type '<U>(ctx: Context<U>) => U' is not assignable to parameter of type 'string'.

Check failure on line 584 in src/generator/writers/writeFunction.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, windows-latest, bun)

Argument of type '<U>(ctx: Context<U>) => U' is not assignable to parameter of type 'string'.

Check failure on line 584 in src/generator/writers/writeFunction.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, windows-latest, bun)

Argument of type '<U>(ctx: Context<U>) => U' is not assignable to parameter of type 'string'.

Check failure on line 584 in src/generator/writers/writeFunction.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, windows-latest, pnpm)

Argument of type '<U>(ctx: Context<U>) => U' is not assignable to parameter of type 'string'.

Check failure on line 584 in src/generator/writers/writeFunction.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, windows-latest, pnpm)

Argument of type '<U>(ctx: Context<U>) => U' is not assignable to parameter of type 'string'.

Check failure on line 584 in src/generator/writers/writeFunction.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, windows-latest, npm)

Argument of type '<U>(ctx: Context<U>) => U' is not assignable to parameter of type 'string'.

Check failure on line 584 in src/generator/writers/writeFunction.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, windows-latest, npm)

Argument of type '<U>(ctx: Context<U>) => U' is not assignable to parameter of type 'string'.

Check failure on line 584 in src/generator/writers/writeFunction.ts

View workflow job for this annotation

GitHub Actions / test (22, windows-latest)

Argument of type '<U>(ctx: Context<U>) => U' is not assignable to parameter of type 'string'.

Check failure on line 584 in src/generator/writers/writeFunction.ts

View workflow job for this annotation

GitHub Actions / test (22, windows-latest)

Argument of type '<U>(ctx: Context<U>) => U' is not assignable to parameter of type 'string'.

Check failure on line 584 in src/generator/writers/writeFunction.ts

View workflow job for this annotation

GitHub Actions / test (22, windows-latest)

Argument of type '<U>(ctx: Context<U>) => U' is not assignable to parameter of type 'string'.

Check failure on line 584 in src/generator/writers/writeFunction.ts

View workflow job for this annotation

GitHub Actions / test (22, windows-latest)

Argument of type '<U>(ctx: Context<U>) => U' is not assignable to parameter of type 'string'.
// fAst.instructions.join(" "),
);
});
if (f.isMutating) {
Expand Down
93 changes: 90 additions & 3 deletions src/grammar/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,102 @@ export type AstAsmFunctionDef = {
name: AstId;
return: AstType | null;
params: AstTypedParameter[];
instructions: AstAsmInstruction[];
expressions: AstAsmExpression[];
id: number;
loc: SrcInfo;
};

export type AstAsmInstruction = string;
export type AstAsmShuffle = {
args: AstId[];
ret: AstNumber[];
};

// NOTE: should it be added to AstNode?
export type AstAsmExpression = AstAsmPrimitive;

export type AstAsmPrimitive =
| AstAsmString
| AstAsmHex
| AstAsmBin
| AstAsmControlReg
| AstAsmStackReg
| AstAsmNumber
| AstAsmInstruction;

/** `"..."` */
export type AstAsmString = {
kind: "asm_string";
value: string;
loc: SrcInfo;
};

/** `x{babe...cafe_}` */
export type AstAsmHex = {
kind: "asm_hex";
/**
* Stores everything inside braces `{}` as is
*
* NOTE: May (?) be changed to bigint or even removed in favor of changed AstAsmNumber
*/
value: string;
/** Does it have an _ right before the } or not? */
isPadded: boolean;
loc: SrcInfo;
};

/** `b{0101...0101}` */
export type AstAsmBin = {
kind: "asm_bin";
/**
* Stores everything inside braces `{}` as is
*
* NOTE: May (?) be changed to bigint or even removed in favor of changed AstAsmNumber
*/
value: string;
loc: SrcInfo;
};

/** `c0`, `c1`, ..., `c15` */
export type AstAsmControlReg = {
kind: "asm_control_reg";
value: bigint;
loc: SrcInfo;
};

/**
* `s0`, `s1`, ..., `s255`
*/
export type AstAsmStackReg = {
kind: "asm_stack_reg";
value: bigint;
/**
* Whether there's a single literal for expressing this stack register in FunC/Fift?
*
* Single literals are written like `s0`, `s1`, ..., `s15`,
* while other stack registers are expressed as `i s()`, where i ∈ [0, 255]
*/
isLiteral: boolean;
loc: SrcInfo;
};

/** NOTE: can later be aliased to Tact number literals */
export type AstAsmNumber = {
kind: "asm_number";
value: bigint;
loc: SrcInfo;
};

export function astAsmNumberToString(n: AstAsmNumber): string {
return n.value.toString(10);
}

/** `MYCODE`, `ADDRSHIFT#MOD`, `IF:`, `XCHG3_l`, `2SWAP`, `-ROT`, etc. */
export type AstAsmInstruction = {
kind: "asm_instruction";
text: string;
loc: SrcInfo;
};

export type AstFunctionDecl = {
kind: "function_decl";
attributes: AstFunctionAttribute[];
Expand Down Expand Up @@ -507,7 +592,9 @@ export type AstFuncId = {
loc: SrcInfo;
};

export function idText(ident: AstId | AstFuncId | AstTypeId): string {
export function idText(
ident: AstId | AstFuncId | AstTypeId | AstAsmInstruction,
): string {
return ident.text;
}

Expand Down
6 changes: 3 additions & 3 deletions src/grammar/compare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
AstNode,
AstFuncId,
AstAsmFunctionDef,
AstAsmInstruction,
AstAsmExpression,
AstDestructMapping,
AstStatementDestruct,
} from "./ast";
Expand Down Expand Up @@ -145,7 +145,7 @@
name: funcName1,
return: returnType1,
params: params1,
instructions: instructions1,

Check failure on line 148 in src/grammar/compare.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, windows-latest, yarn)

Property 'instructions' does not exist on type 'AstAsmFunctionDef'.

Check failure on line 148 in src/grammar/compare.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, windows-latest, bun)

Property 'instructions' does not exist on type 'AstAsmFunctionDef'.

Check failure on line 148 in src/grammar/compare.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, windows-latest, pnpm)

Property 'instructions' does not exist on type 'AstAsmFunctionDef'.

Check failure on line 148 in src/grammar/compare.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, windows-latest, npm)

Property 'instructions' does not exist on type 'AstAsmFunctionDef'.

Check failure on line 148 in src/grammar/compare.ts

View workflow job for this annotation

GitHub Actions / test (22, windows-latest)

Property 'instructions' does not exist on type 'AstAsmFunctionDef'.

Check failure on line 148 in src/grammar/compare.ts

View workflow job for this annotation

GitHub Actions / test (22, windows-latest)

Property 'instructions' does not exist on type 'AstAsmFunctionDef'.
} = node1 as AstAsmFunctionDef;
const {
shuffle: shuffle2,
Expand All @@ -153,7 +153,7 @@
name: funcName2,
return: returnType2,
params: params2,
instructions: instructions2,

Check failure on line 156 in src/grammar/compare.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, windows-latest, yarn)

Property 'instructions' does not exist on type 'AstAsmFunctionDef'.

Check failure on line 156 in src/grammar/compare.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, windows-latest, bun)

Property 'instructions' does not exist on type 'AstAsmFunctionDef'.

Check failure on line 156 in src/grammar/compare.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, windows-latest, pnpm)

Property 'instructions' does not exist on type 'AstAsmFunctionDef'.

Check failure on line 156 in src/grammar/compare.ts

View workflow job for this annotation

GitHub Actions / test-blueprint (22, windows-latest, npm)

Property 'instructions' does not exist on type 'AstAsmFunctionDef'.

Check failure on line 156 in src/grammar/compare.ts

View workflow job for this annotation

GitHub Actions / test (22, windows-latest)

Property 'instructions' does not exist on type 'AstAsmFunctionDef'.

Check failure on line 156 in src/grammar/compare.ts

View workflow job for this annotation

GitHub Actions / test (22, windows-latest)

Property 'instructions' does not exist on type 'AstAsmFunctionDef'.
} = node2 as AstAsmFunctionDef;
return (
this.compareArray(shuffle1.args, shuffle2.args) &&
Expand Down Expand Up @@ -850,8 +850,8 @@
}

private compareAsmInstructions(
instructions1: AstAsmInstruction[],
instructions2: AstAsmInstruction[],
instructions1: AstAsmExpression[],
instructions2: AstAsmExpression[],
): boolean {
if (instructions1.length !== instructions2.length) {
return false;
Expand Down
51 changes: 35 additions & 16 deletions src/grammar/grammar.ohm
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Tact {
ModuleFunction = FunctionDefinition
| AsmFunction

AsmFunction = "asm" AsmShuffle? FunctionAttribute* fun id Parameters (":" Type)? "{" AsmInstruction* "}"
AsmFunction = "asm" AsmShuffle? FunctionAttribute* fun id Parameters (":" Type)? "{" AsmExpression* "}"

ModuleConstant = ConstantDefinition

Expand Down Expand Up @@ -99,21 +99,40 @@ Tact {
| external "(" Parameter? ")" "{" Statement* "}" --externalRegular
| external "(" stringLiteral ")" "{" Statement* "}" --externalComment

AsmInstruction = "[" &#whiteSpace AsmInstruction* "]" &#whiteSpace --internal
| "{" &#whiteSpace AsmInstruction* "}" &#whiteSpace --list
| "({)" &#whiteSpace AsmInstruction* "(})" &#whiteSpace --listNoStateCheck
| ("abort\"" | ".\"" | "+\"" | "\"") (~"\"" any)* "\"" &#whiteSpace --string
| "'" &#whiteSpace asmInstruction --tick
| "char" &#whiteSpace (~whiteSpace any) &#whiteSpace --char
| ("x{" | "B{") ~#whiteSpace hexDigit* ~#whiteSpace ("_" ~#whiteSpace)? "}" &#whiteSpace --hexLiteral
| "b{" ~#whiteSpace binDigit* ~#whiteSpace "}" &#whiteSpace --binLiteral
| asmInstruction --custom

// Instructions exclude some begin and end words to ensure correct parse
asmInstruction = ~(("[" | "]" | "{" | "}" | "({)" | "(})") ~asmWord) asmWord

// A chunk of non-whitespace characters forms a word in Fift
asmWord = (~whiteSpace any)+
// FIXME: prettyPrinter.ts + writeFunction.ts, hash.ts, compare.ts
AsmExpression = "{" (~"}" any)* "}" --list // used only for error messages
| asmPrimitive

// Various primitives and instructions not forming a block
asmPrimitive = asmString
| asmHex
| asmBin
| asmControlReg
| asmStackReg
| asmStackReg256
| asmNumber
| asmInstruction // must be last

// It's here mostly to provide better error messages
// for people coming from FunC/Fift background
asmString (string) = "\"" (~"\"" any)* "\""
asmHex (hex bitstring) = "x{" hexDigit* "_"? "}"
asmBin (binary bitstring) = "b{" binDigit* "}"
asmControlReg (control register) = "c" digit digit?
asmStackReg (stack register) = "s" digit digit? digit?
// It's here mostly to provide better error messages
// for people coming from FunC/Fift background,
// gently guiding them towards simpler s0...s255 syntax
asmStackReg256 (indexed stack register) = digit digit? digit? whiteSpace+ "s()"
asmNumber (number) = "-"? digit+ ~(letterAscii | "_" | "#" | ":")

// To ensure correct parse:
// 1. Instructions cannot contain braces
// 2. Cannot be braces themselves
// 3. Cannot contain lowercase letters, except for the very last position
asmInstruction (TVM instruction) = ~(("{" | "}") ~asmWord) asmWord
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately all newline characters get parsed as spaces and are completely erased from AST. There is no way to pretty-print this at all.

I can imagine only one way to make this work without adding ; or some other explicit way to terminate lines: put this into a separate grammar with its own spaces rule.

Copy link
Member Author

@novusnota novusnota Nov 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I'm thinking of the Prettier's approach with trying to fit as much as allowed in 80, 120 or other column limits, and put all excesses on new lines. Or, to simply put a newline character after each instruction — to keep all prior primitives required for it on the same line.

The second option sounds best from legibility perspective as well:

asm fun showcase(a: Int, b: Int, c: Int): Int {
    s0 s1 s2 XCHG3
    s1 s2 s0 XCHG3
    DROP2
}

Copy link
Member Author

@novusnota novusnota Nov 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, and it would be nice to also check if the instructions are written on the same line with the opening and closing braces { ... }, i.e. when the start row of the function body equals its end row. Because if that's the case, we should respect the author's intent and inline everything there: add spaces after instructions, not newlines.

asmWord = "-"? asmWordPart+ "l"?
asmWordPart = letterAsciiUC | digit | "_" | "#" | ":"

Statement = StatementLet
| StatementBlock
Expand Down
16 changes: 1 addition & 15 deletions src/grammar/grammar.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { parse } from "./grammar";
import { AstModule, SrcInfo, __DANGER_resetNodeId } from "./ast";
import { SrcInfo, __DANGER_resetNodeId } from "./ast";
import { loadCases } from "../utils/loadCases";

expect.addSnapshotSerializer({
Expand All @@ -12,20 +12,6 @@ describe("grammar", () => {
__DANGER_resetNodeId();
});

// Test parsing of known Fift projects, wrapped in asm functions of Tact
for (const r of loadCases(__dirname + "/test-asm/")) {
it("should parse " + r.name, () => {
const parsed: AstModule | undefined = parse(
r.code,
"<unknown>",
"user",
);

// Don't produce snapshots
expect(parsed).toBeDefined();
});
}

for (const r of loadCases(__dirname + "/test/")) {
it("should parse " + r.name, () => {
expect(parse(r.code, "<unknown>", "user")).toMatchSnapshot();
Expand Down
Loading
Loading