Skip to content

Commit 5d1639a

Browse files
authored
Merge pull request #9 from distributed-lab/feature/arrays-support
Add resolver for arrays in the component main
2 parents 7a4d0c7 + 43e3ae9 commit 5d1639a

18 files changed

+377
-95
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@distributedlab/circom-parser",
33
"description": "Circom circuit parser built with ANTLR4",
4-
"version": "0.2.1",
4+
"version": "0.2.2",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",
77
"files": [

src/ExtendedCircomParser.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,24 @@ export class ExtendedCircomParser extends CircomParser {
1212
parserErrorListener: ErrorListener<Token>;
1313
lexerErrorListener: ErrorListener<number>;
1414

15-
constructor(tokens: antlr4.CommonTokenStream, lexer: CircomLexer) {
15+
fileIdentifier: string;
16+
17+
constructor(
18+
fileIdentifier: string,
19+
tokens: antlr4.CommonTokenStream,
20+
lexer: CircomLexer,
21+
) {
1622
super(tokens);
1723

1824
this.lexer = lexer;
19-
this.lexerErrorListener = new ErrorListener();
20-
this.parserErrorListener = new ErrorListener();
25+
this.lexerErrorListener = new ErrorListener(fileIdentifier);
26+
this.parserErrorListener = new ErrorListener(fileIdentifier);
2127

2228
this.initErrorListeners();
2329

2430
this.buildParseTrees = true;
31+
32+
this.fileIdentifier = fileIdentifier;
2533
}
2634

2735
circuit() {
@@ -42,11 +50,11 @@ export class ExtendedCircomParser extends CircomParser {
4250
}
4351

4452
initErrorListeners() {
45-
this.parserErrorListener = new ErrorListener();
53+
this.parserErrorListener = new ErrorListener(this.fileIdentifier);
4654
this.removeErrorListeners();
4755
this.addErrorListener(this.parserErrorListener);
4856

49-
this.lexerErrorListener = new ErrorListener();
57+
this.lexerErrorListener = new ErrorListener(this.fileIdentifier);
5058
this.lexer.removeErrorListeners();
5159
this.lexer.addErrorListener(this.lexerErrorListener);
5260
}

src/ExtendedCircomVisitor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export class ExtendedCircomVisitor<Result> extends CircomVisitor<Result> {
1515

1616
protected addError(message: string, context: ParserRuleContext) {
1717
this.errors.push({
18-
templateName: this.templateIdentifier,
18+
fileIdentifier: this.templateIdentifier,
1919
message,
2020
line: context.start.line,
2121
column: context.start.column,

src/errors/ErrorListener.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@ import { ParserErrorItem } from "../types";
55
class ErrorListener<TSymbol> extends AntlrErrorListener<TSymbol> {
66
private readonly _errors: ParserErrorItem[];
77

8-
constructor() {
8+
constructor(public fileIdentifier: string) {
99
super();
1010

1111
this._errors = [];
1212
}
1313

14-
// TODO: improve error handling
1514
syntaxError(
1615
recognizer: Recognizer<TSymbol>,
1716
offendingSymbol: TSymbol,
@@ -23,8 +22,8 @@ class ErrorListener<TSymbol> extends AntlrErrorListener<TSymbol> {
2322
message,
2423
line,
2524
column,
25+
fileIdentifier: this.fileIdentifier,
2626
context: null as any,
27-
templateName: null,
2827
});
2928
}
3029

src/index.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,20 @@ import { CircomLexer } from "./generated";
66
import { ExtendedCircomParser } from "./ExtendedCircomParser";
77

88
export function getCircomParser(source: string): ExtendedCircomParser {
9-
const inputStream = fs.existsSync(source)
10-
? antlr4.CharStreams.fromPathSync(source, "utf8")
11-
: antlr4.CharStreams.fromString(source);
9+
let inputStream: antlr4.CharStream;
10+
let fileIdentifier = "Built from source";
11+
12+
if (fs.existsSync(source)) {
13+
inputStream = antlr4.CharStreams.fromPathSync(source, "utf8");
14+
fileIdentifier = source;
15+
} else {
16+
inputStream = antlr4.CharStreams.fromString(source);
17+
}
1218

1319
const lexer = new CircomLexer(inputStream);
1420
const tokens = new antlr4.CommonTokenStream(lexer);
1521

16-
return new ExtendedCircomParser(tokens, lexer);
22+
return new ExtendedCircomParser(fileIdentifier, tokens, lexer);
1723
}
1824

1925
export * from "./types";

src/types/common.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
export type CircomValueType = bigint | CircomValueType[];
22

33
export type VariableContext = Record<string, CircomValueType>;
4+
5+
export type VariableContextWithNull = Record<string, CircomValueType | null>;

src/types/errors.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ParserRuleContext } from "antlr4";
22

33
export type ParserErrorItem = {
4-
templateName: string | null;
4+
fileIdentifier: string;
55
message: string;
66
line: number;
77
column: number;

src/utils/ExpressionHelper.ts

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,10 @@ class ExpressionVisitor extends ExtendedCircomVisitor<CircomValueType | null> {
121121
ctx: PIdentifierStatementContext,
122122
): CircomValueType | null => {
123123
if (ctx.identifierStatement().idetifierAccess_list().length == 0) {
124-
const variableName =
124+
const variableValue =
125125
this.variableContext[ctx.identifierStatement().ID().getText()];
126126

127-
if (variableName === undefined) {
127+
if (variableValue === undefined) {
128128
this.addError(
129129
`Variable ${ctx.identifierStatement().ID().getText()} is not defined`,
130130
ctx.identifierStatement(),
@@ -133,14 +133,36 @@ class ExpressionVisitor extends ExtendedCircomVisitor<CircomValueType | null> {
133133
return null;
134134
}
135135

136-
return variableName;
136+
return variableValue;
137137
}
138138

139-
this.addError(
140-
"IdentifierStatement is not supported with access references",
141-
ctx,
142-
);
143-
return null;
139+
const reference = ctx
140+
.identifierStatement()
141+
.idetifierAccess_list()
142+
.map((access) => access.getText())
143+
.join("");
144+
145+
if (reference.indexOf(".") !== -1) {
146+
this.addError(
147+
"IdentifierStatement is not supported with access references that are not arrays",
148+
ctx,
149+
);
150+
151+
return null;
152+
}
153+
154+
const variableName = ctx.identifierStatement().ID().getText() + reference;
155+
156+
if (this.variableContext[variableName] === undefined) {
157+
this.addError(
158+
`Variable ${variableName} is not defined`,
159+
ctx.identifierStatement(),
160+
);
161+
162+
return null;
163+
}
164+
165+
return this.variableContext[variableName];
144166
};
145167

146168
visitPUnderscore = (_ctx: PUnderscoreContext): CircomValueType | null => {

src/utils/common.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
import { SimpleIdentifierListContext } from "../generated";
2+
import {
3+
CircomValueType,
4+
VariableContext,
5+
VariableContextWithNull,
6+
} from "../types";
27

38
export function parseSimpleIdentifierList(
49
ctx: SimpleIdentifierListContext,
@@ -15,3 +20,115 @@ export function parseSimpleIdentifierList(
1520

1621
return result;
1722
}
23+
24+
export function buildVariableContext(
25+
names: string[],
26+
values: CircomValueType[],
27+
): VariableContext {
28+
if (names.length !== values.length) {
29+
throw new Error("Names and values must have the same length");
30+
}
31+
32+
const context: VariableContext = {};
33+
34+
for (let i = 0; i < names.length; i++) {
35+
const bindContext = bindVariableContext(
36+
names[i],
37+
getArrayDimensions(values[i]),
38+
values[i],
39+
);
40+
41+
for (const key in bindContext) {
42+
if (bindContext[key] !== null) {
43+
context[key] = bindContext[key];
44+
}
45+
}
46+
}
47+
48+
return context;
49+
}
50+
51+
export function getArrayDimensions(value: CircomValueType): number[] {
52+
if (Array.isArray(value)) {
53+
return [value.length, ...getArrayDimensions(value[0])];
54+
}
55+
56+
return [];
57+
}
58+
59+
export function bindVariableContext(
60+
variableName: string,
61+
dimensions: number[],
62+
values: CircomValueType,
63+
): VariableContextWithNull {
64+
const context: VariableContextWithNull = {};
65+
const resolved = resolveDimensions(variableName, dimensions);
66+
67+
for (const variable of resolved) {
68+
try {
69+
context[variable] = parseVariable(
70+
values,
71+
variable.replace(variableName, ""),
72+
);
73+
} catch {
74+
context[variable] = null;
75+
}
76+
}
77+
78+
return context;
79+
}
80+
81+
export function resolveDimensions(
82+
variableName: string,
83+
dimensions: number[],
84+
): string[] {
85+
return internalResolveDimensions([variableName], dimensions, 0);
86+
}
87+
88+
// reference MUST be similar to [0][1]
89+
function parseVariable(
90+
value: CircomValueType,
91+
reference: string,
92+
): CircomValueType {
93+
const parts = reference
94+
.split("[")
95+
.map((part) => part.replace("]", ""))
96+
.filter((part) => part !== "")
97+
.map((part) => parseInt(part));
98+
99+
return getReferenceValueInternal(value, parts);
100+
}
101+
102+
function getReferenceValueInternal(
103+
value: CircomValueType,
104+
reference: number[],
105+
): CircomValueType {
106+
if (reference.length === 0) {
107+
return value;
108+
}
109+
110+
if (!Array.isArray(value)) {
111+
throw new Error("INTERNAL ERROR! Reference is invalid");
112+
}
113+
114+
return getReferenceValueInternal(value[reference[0]], reference.slice(1));
115+
}
116+
117+
function internalResolveDimensions(
118+
variables: string[],
119+
dimensions: number[],
120+
layer: number,
121+
): string[] {
122+
if (layer >= dimensions.length) {
123+
return variables;
124+
}
125+
126+
const result: string[] = [];
127+
for (let i = 0; i < dimensions[layer]; i++) {
128+
for (const variable of variables) {
129+
result.push(`${variable}[${i}]`);
130+
}
131+
}
132+
133+
return internalResolveDimensions(result, dimensions, layer + 1);
134+
}

0 commit comments

Comments
 (0)