Skip to content

Commit

Permalink
Replace value_parser with JISON parser
Browse files Browse the repository at this point in the history
  • Loading branch information
cd1m0 committed Mar 8, 2024
1 parent 239d197 commit 457acd2
Show file tree
Hide file tree
Showing 8 changed files with 748 additions and 61 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ src/gen/translate.ts
src/gen/declarations.ts
src/lib/souffle/value_parser.ts
src/lib/souffle/parser/souffle_parser_gen.js
src/lib/souffle/parser/souffle_value_parser_gen.js
functors/functors.o
functors/libfunctors.so
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
"copy-dl": "cp -r src/lib/analyses dist/lib && cp -r src/lib/detectors dist/lib",
"copy-functors": "cp -r functors dist/",
"build-parser": "jison src/lib/souffle/parser/souffle.jison -o src/lib/souffle/parser/souffle_parser_gen.js -m commonjs && cp src/lib/souffle/parser/souffle_parser_gen.js dist/lib/souffle/parser/souffle_parser_gen.js",
"build-expr-parser": "jison src/lib/souffle/parser/souffle_value.jison -o src/lib/souffle/parser/souffle_value_parser_gen.js -m commonjs && cp src/lib/souffle/parser/souffle_value_parser_gen.js dist/lib/souffle/parser/souffle_value_parser_gen.js",
"build-value-parser": "tspegjs -o src/lib/souffle/value_parser.ts --custom-header-file src/lib/souffle/value_header.ts --allowed-start-rules Value --cache src/lib/souffle/values.pegjs",
"build-parsers": "npm run build-parser && npm run build-expr-parser",
"build-functors": "./scripts/build_functors.sh",
"build": "npm run clean && npm run gen-translation-modules && npm run build-value-parser && npm run transpile && npm run build-parser && chmod a+x dist/bin/cli.js && npm run copy-dl && npm run build-functors && npm run copy-functors",
"build": "npm run clean && npm run gen-translation-modules && npm run build-value-parser && npm run transpile && npm run build-parsers && chmod a+x dist/bin/cli.js && npm run copy-dl && npm run build-functors && npm run copy-functors",
"test": "c8 mocha",
"lint": "eslint src/ test/ --ext=ts",
"lint:fix": "eslint src/ test/ --ext=ts --fix",
Expand Down
136 changes: 86 additions & 50 deletions src/lib/souffle/fact.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,95 @@
import * as sol from "solc-typed-ast";
import { Relation } from "./relation";
import { RecordT, SubT, DatalogType, NumberT, SymbolT, AliasT } from "./types";
import { ParsedFieldVal, parseValue } from "./value_parser";
import { zip } from "../utils";
import { RecordT, SubT, DatalogType, NumberT, SymbolT, AliasT, ADTT } from "./types";
import { parseExpression } from "./parser";
import * as ast from "./ast";

function getBranch(typ: ADTT, branch: string): [string, Array<[string, DatalogType]>] {
const filtered = typ.branches.filter(([name]) => name === branch);
sol.assert(
filtered.length === 1,
`Couldn't find branch hamed {0} in ADT {1}`,
branch,
typ.name
);

return filtered[0];
}

/**
* Convert a literal Expression AST to a FieldVal.
*/
function literalExprToVal(expr: ast.Expression, typ: DatalogType): FieldVal {
if (expr instanceof ast.Num) {
return expr.value;
}

if (expr instanceof ast.StringLiteral) {
return expr.value;
}

if (expr instanceof ast.Nil) {
sol.assert(typ instanceof RecordT, ``);
return null;
}

export type FieldVal = string | number | bigint | { [field: string]: FieldVal } | null;
if (expr instanceof ast.RecordLiteral) {
sol.assert(typ instanceof RecordT, ``);
sol.assert(typ.fields.length === expr.args.length, ``);

return Object.fromEntries(
expr.args.map((_, i) => [
typ.fields[i][0],
literalExprToVal(expr.args[i], typ.fields[i][1])
])
);
}

if (expr instanceof ast.ADTLiteral) {
sol.assert(typ instanceof ADTT, ``);
const branchT = getBranch(typ, expr.branch);

return [
expr.branch,
Object.fromEntries(
expr.args.map((_, i) => [
branchT[1][i][0],
literalExprToVal(expr.args[i], branchT[1][i][1])
])
)
];
}

throw new Error(`NYI translating ${expr.pp()} of type ${typ.name} to FieldVal`);
}

export function parseValueInt(source: string, typ: DatalogType): FieldVal {
return literalExprToVal(parseExpression(source), typ);
}

export type RecordVal = { [field: string]: FieldVal };
export type ADTVal = [string, RecordVal];
export type FieldVal = string | bigint | number | RecordVal | ADTVal | null;

function isADT(v: FieldVal): v is ADTVal {
return v instanceof Array;
}

function isRecord(v: FieldVal): v is RecordVal {
return v instanceof Object && !isADT(v);
}

// TODO: A lot of functions with very similar structure. Code duplication probably here

function fieldValToJSON(val: FieldVal, typ: DatalogType): any {
if (typ === SymbolT || typ == NumberT) {
if (typ === SymbolT) {
return val;
}

if (typ == NumberT) {
return Number(val);
}

if (typ instanceof SubT) {
return fieldValToJSON(val, typ.parentT);
}
Expand All @@ -26,7 +103,7 @@ function fieldValToJSON(val: FieldVal, typ: DatalogType): any {
return null;
}

sol.assert(val instanceof Object, `Expected an object in fieldValToJSON, not ${val}`);
sol.assert(isRecord(val), `Expected a Record in fieldValToJSON, not ${val}`);
return typ.fields.map(([name, fieldT]) => fieldValToJSON(val[name], fieldT));
}

Expand Down Expand Up @@ -55,54 +132,13 @@ export function ppFieldVal(val: FieldVal, typ: DatalogType): string {
return `nil`;
}

sol.assert(val instanceof Object, `Expected an object in ppFieldVal`);
sol.assert(isRecord(val), `Expected an object in ppFieldVal`);
return `[${typ.fields.map(([name, fieldT]) => ppFieldVal(val[name], fieldT)).join(", ")}]`;
}

throw new Error(`NYI type ${typ.name}`);
}

export function translateVal(raw: ParsedFieldVal, typ: DatalogType): FieldVal {
if (typ === NumberT) {
sol.assert(
typeof raw === "number",
`Expected a number when translating a number, not {0}`,
typeof raw
);
return raw;
}

if (typ === SymbolT) {
sol.assert(typeof raw === "string", `Expected a string when translating a symbol`);
return raw;
}

if (typ instanceof SubT) {
return translateVal(raw, typ.parentT);
}

if (typ instanceof AliasT) {
return translateVal(raw, typ.originalT);
}

if (typ instanceof RecordT) {
if (raw === null) {
return null;
}

sol.assert(raw instanceof Array && raw.length === typ.fields.length, ``);

return Object.fromEntries(
zip(
typ.fields.map((x) => x[0]),
raw.map((x, i) => translateVal(x, typ.fields[i][1]))
)
);
}

throw new Error(`NYI type ${typ.name}`);
}

function parseFieldValFromCsv(val: any, typ: DatalogType): FieldVal {
if (typ === NumberT) {
sol.assert(typeof val === "number", `Expected a number`);
Expand All @@ -123,7 +159,7 @@ function parseFieldValFromCsv(val: any, typ: DatalogType): FieldVal {

if (typ instanceof RecordT) {
sol.assert(typeof val === "string", `Expected a string`);
return translateVal(parseValue(val), typ);
return parseValueInt(val, typ);
}

throw new Error(`NYI datalog type ${typ}`);
Expand All @@ -150,7 +186,7 @@ function parseFieldValFromSQL(val: any, typ: DatalogType): FieldVal {

if (typ instanceof RecordT) {
if (typeof val === "string") {
return translateVal(parseValue(val), typ);
return parseValueInt(val, typ);
}

throw new Error(`NYI parsing record types from ${val}`);
Expand Down
1 change: 0 additions & 1 deletion src/lib/souffle/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@ export * from "./instance";
export * from "./souffle";
export * from "./types";
export * from "./relation";
export { parseValue } from "./value_parser";
2 changes: 1 addition & 1 deletion src/lib/souffle/parser/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from "./parser";
export * from "./parsers";
8 changes: 0 additions & 8 deletions src/lib/souffle/parser/parser.ts

This file was deleted.

14 changes: 14 additions & 0 deletions src/lib/souffle/parser/parsers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as ast from "../ast";

const ProgramParser = (require("./souffle_parser_gen") as any).Parser;
const ExprParser = (require("./souffle_value_parser_gen") as any).Parser;
const programParser = new ProgramParser();
const exprParser = new ExprParser();

export function parseProgram(s: string): ast.Program {
return programParser.parse(s);
}

export function parseExpression(s: string): ast.Expression {
return exprParser.parse(s);
}
Loading

0 comments on commit 457acd2

Please sign in to comment.