init
This commit is contained in:
commit
b805d9b48f
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
parser.out.*
|
||||||
|
|
36
ast.ts
Normal file
36
ast.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
export type Struct = {
|
||||||
|
id: number;
|
||||||
|
ident: string;
|
||||||
|
loc: Loc;
|
||||||
|
generics: Generics | null;
|
||||||
|
fields: Field[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Generics = { params: string[] };
|
||||||
|
|
||||||
|
export type Field = {
|
||||||
|
ident: string;
|
||||||
|
loc: Loc;
|
||||||
|
ty: Ty;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Ty = {
|
||||||
|
id: number;
|
||||||
|
loc: Loc;
|
||||||
|
kind: TyKind;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TyKind =
|
||||||
|
| { tag: "array"; length: number | null; ty: Ty }
|
||||||
|
| { tag: "generic"; ident: string; args: Ty[] }
|
||||||
|
| { tag: "struct_literal"; fields: Field[] }
|
||||||
|
| { tag: "ident"; ident: string }
|
||||||
|
| { tag: "int"; value: number };
|
||||||
|
|
||||||
|
export type Loc = {
|
||||||
|
source: string;
|
||||||
|
start: Pos;
|
||||||
|
end: Pos;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Pos = { offset: number; line: number; column: number };
|
15
defs.structemit
Normal file
15
defs.structemit
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
struct Product<T> {
|
||||||
|
product_id: int,
|
||||||
|
name: str,
|
||||||
|
price_dkk_cent: int,
|
||||||
|
amount: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Receipt {
|
||||||
|
receipt_id: int,
|
||||||
|
timestamp: str,
|
||||||
|
products: Product<int>[],
|
||||||
|
}
|
||||||
|
|
||||||
|
// vim: syntax=rust commentstring=//\ %s
|
5
deno.jsonc
Normal file
5
deno.jsonc
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"fmt": {
|
||||||
|
"indentWidth": 4
|
||||||
|
}
|
||||||
|
}
|
37
deno.lock
generated
Normal file
37
deno.lock
generated
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"version": "4",
|
||||||
|
"specifiers": {
|
||||||
|
"jsr:@std/yaml@*": "1.0.5",
|
||||||
|
"npm:peggy@*": "4.2.0"
|
||||||
|
},
|
||||||
|
"jsr": {
|
||||||
|
"@std/yaml@1.0.5": {
|
||||||
|
"integrity": "71ba3d334305ee2149391931508b2c293a8490f94a337eef3a09cade1a2a2742"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"npm": {
|
||||||
|
"@peggyjs/from-mem@1.3.5": {
|
||||||
|
"integrity": "sha512-oRyzXE7nirAn+5yYjCdWQHg3EG2XXcYRoYNOK8Quqnmm+9FyK/2YWVunwudlYl++M3xY+gIAdf0vAYS+p0nKfQ==",
|
||||||
|
"dependencies": [
|
||||||
|
"semver"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"commander@12.1.0": {
|
||||||
|
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="
|
||||||
|
},
|
||||||
|
"peggy@4.2.0": {
|
||||||
|
"integrity": "sha512-ZjzyJYY8NqW8JOZr2PbS/J0UH/hnfGALxSDsBUVQg5Y/I+ZaPuGeBJ7EclUX2RvWjhlsi4pnuL1C/K/3u+cDeg==",
|
||||||
|
"dependencies": [
|
||||||
|
"@peggyjs/from-mem",
|
||||||
|
"commander",
|
||||||
|
"source-map-generator"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"semver@7.6.3": {
|
||||||
|
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="
|
||||||
|
},
|
||||||
|
"source-map-generator@0.8.0": {
|
||||||
|
"integrity": "sha512-psgxdGMwl5MZM9S3FWee4EgsEaIjahYV5AzGnwUvPhWeITz/j6rKpysQHlQ4USdxvINlb8lKfWGIXwfkrgtqkA=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
generate_parser.sh
Executable file
6
generate_parser.sh
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# npx peggy --dts structemit.pegjs --format es -m -o parser.out.js
|
||||||
|
npx peggy -c parser.config.js
|
||||||
|
|
||||||
|
|
428
main.ts
Normal file
428
main.ts
Normal file
@ -0,0 +1,428 @@
|
|||||||
|
import * as parser from "./parser.out.js";
|
||||||
|
import * as yaml from "jsr:@std/yaml";
|
||||||
|
import * as ast from "./ast.ts";
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const filename = Deno.args[0];
|
||||||
|
const text = await Deno.readTextFile(filename);
|
||||||
|
|
||||||
|
const ast = ((): ast.Struct[] | undefined => {
|
||||||
|
try {
|
||||||
|
return parser.parse(text, {
|
||||||
|
grammarSource: filename,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof parser.SyntaxError) {
|
||||||
|
console.error(
|
||||||
|
e.format([{ source: filename, text }]),
|
||||||
|
);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
if (!ast) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//console.log(yaml.stringify(ast));
|
||||||
|
|
||||||
|
const rep = new Reporter(text);
|
||||||
|
|
||||||
|
const re = new Resolver(ast, rep).resolve();
|
||||||
|
const units = new DependencyTracer(ast, re, rep).trace();
|
||||||
|
const unitMap = mapUnitIds(units);
|
||||||
|
|
||||||
|
new CircularDependencyFinder(unitMap, rep)
|
||||||
|
.findCircularDependencies();
|
||||||
|
|
||||||
|
const entries = entryPoints(units, rep);
|
||||||
|
|
||||||
|
if (rep.errorOccured) {
|
||||||
|
console.log("Errors occured. Stopping...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type MonoUnit = {
|
||||||
|
id: number;
|
||||||
|
unit: Unit;
|
||||||
|
generics?: Map<number, ast.Ty>;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Monomorphizer {
|
||||||
|
public constructor(
|
||||||
|
private units: Unit[],
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public monomorph(): MonoUnit[] {
|
||||||
|
const monoUnits: MonoUnit[] = [];
|
||||||
|
for (const unit of this.units) {
|
||||||
|
}
|
||||||
|
return monoUnits;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CircularDependencyFinder {
|
||||||
|
private hasCirc = new Set<number>();
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private units: Map<number, Unit>,
|
||||||
|
private rep: Reporter,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public findCircularDependencies() {
|
||||||
|
for (const id of this.units.keys()) {
|
||||||
|
this.searchDependencies(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private searchDependencies(
|
||||||
|
id: number,
|
||||||
|
defined = new Set<number>(),
|
||||||
|
) {
|
||||||
|
if (this.hasCirc.has(id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
defined.add(id);
|
||||||
|
const unit = this.units.get(id)!;
|
||||||
|
for (const depId of unit.dependencies) {
|
||||||
|
const dep = this.units.get(depId)!;
|
||||||
|
if (defined.has(depId)) {
|
||||||
|
this.rep.error(
|
||||||
|
`circular dependency between '${unit.struct.ident}' and '${dep.struct.ident}'`,
|
||||||
|
);
|
||||||
|
this.hasCirc.add(unit.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.searchDependencies(depId, new Set(defined));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function entryPoints(units: Unit[], rep: Reporter): Set<number> {
|
||||||
|
const entries = units.filter((unit) => unit.dependencies.size === 0);
|
||||||
|
if (entries.length === 0) {
|
||||||
|
rep.error(
|
||||||
|
"no entry points in dependency graph (everything depends on something else)",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return new Set(entries.map((entry) => entry.struct.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapUnitIds(units: Unit[]): Map<number, Unit> {
|
||||||
|
return new Map(units.map((v, i) => [i, v]));
|
||||||
|
}
|
||||||
|
|
||||||
|
type Unit = {
|
||||||
|
id: number;
|
||||||
|
struct: ast.Struct;
|
||||||
|
dependencies: Set<number>;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DependencyTracer {
|
||||||
|
public constructor(
|
||||||
|
private ast: ast.Struct[],
|
||||||
|
private re: Map<number, Resol>,
|
||||||
|
private rep: Reporter,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public trace(): Unit[] {
|
||||||
|
const units: Unit[] = [];
|
||||||
|
for (const struct of this.ast) {
|
||||||
|
const dependencies = new StructTracer(struct, this.re).trace();
|
||||||
|
units.push({ id: struct.id, struct, dependencies });
|
||||||
|
}
|
||||||
|
return units;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StructTracer {
|
||||||
|
private dependencies = new Set<number>();
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private struct: ast.Struct,
|
||||||
|
private re: Map<number, Resol>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public trace(): Set<number> {
|
||||||
|
for (const field of this.struct.fields) {
|
||||||
|
this.traceTy(field.ty);
|
||||||
|
}
|
||||||
|
return this.dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
private traceTy(ty: ast.Ty) {
|
||||||
|
const k = ty.kind;
|
||||||
|
switch (k.tag) {
|
||||||
|
case "array":
|
||||||
|
this.traceTy(k.ty);
|
||||||
|
return;
|
||||||
|
case "generic": {
|
||||||
|
const re = this.re.get(ty.id)!;
|
||||||
|
if (re.tag !== "struct") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.dependencies.add(re.struct.id);
|
||||||
|
for (const ty of k.args) {
|
||||||
|
this.traceTy(ty);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case "struct_literal":
|
||||||
|
for (const field of k.fields) {
|
||||||
|
this.traceTy(field.ty);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case "ident": {
|
||||||
|
const re = this.re.get(ty.id)!;
|
||||||
|
if (re.tag !== "struct") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.dependencies.add(re.struct.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case "int":
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const _: never = k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Resol =
|
||||||
|
| { tag: "error" }
|
||||||
|
| { tag: "undefined" }
|
||||||
|
| { tag: "struct"; struct: ast.Struct }
|
||||||
|
| { tag: "generic"; struct: ast.Struct; idx: number }
|
||||||
|
| { tag: "primitive" };
|
||||||
|
|
||||||
|
type Rib = {
|
||||||
|
defs: Map<string, Resol>;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Resolver {
|
||||||
|
private resols = new Map<number, Resol>();
|
||||||
|
private ribs: Rib[] = [{ defs: new Map() }];
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private ast: ast.Struct[],
|
||||||
|
private rep: Reporter,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public resolve(): Map<number, Resol> {
|
||||||
|
for (const struct of this.ast) {
|
||||||
|
const res = this.define(struct.ident, { tag: "struct", struct });
|
||||||
|
if (res === "already defined") {
|
||||||
|
this.reportAlreadyDefined(struct.ident, struct.loc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const struct of this.ast) {
|
||||||
|
this.ribs.push({ defs: new Map() });
|
||||||
|
if (struct.generics) {
|
||||||
|
for (const [idx, ident] of struct.generics.params.entries()) {
|
||||||
|
const res = this.define(ident, {
|
||||||
|
tag: "generic",
|
||||||
|
struct,
|
||||||
|
idx,
|
||||||
|
});
|
||||||
|
if (res === "already defined") {
|
||||||
|
this.reportAlreadyDefined(ident, struct.loc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const fields = new Map<string, ast.Field>();
|
||||||
|
for (const field of struct.fields) {
|
||||||
|
this.resolveTy(field.ty);
|
||||||
|
if (fields.has(field.ident)) {
|
||||||
|
this.rep.error(
|
||||||
|
`field '${field.ident}' already defined`,
|
||||||
|
field.loc,
|
||||||
|
);
|
||||||
|
const otherField = fields.get(field.ident)!;
|
||||||
|
this.rep.info(
|
||||||
|
`original field '${otherField.ident}' defined here`,
|
||||||
|
otherField.loc,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fields.set(field.ident, field);
|
||||||
|
}
|
||||||
|
this.ribs.pop();
|
||||||
|
}
|
||||||
|
return this.resols;
|
||||||
|
}
|
||||||
|
|
||||||
|
private resolveTy(ty: ast.Ty) {
|
||||||
|
const k = ty.kind;
|
||||||
|
switch (k.tag) {
|
||||||
|
case "array":
|
||||||
|
this.resolveTy(k.ty);
|
||||||
|
return;
|
||||||
|
case "generic": {
|
||||||
|
const re = this.resolveIdent(k.ident);
|
||||||
|
if (re.tag !== "struct") {
|
||||||
|
this.rep.error(`identifier '${k.ident}' is not a struct`);
|
||||||
|
this.resols.set(ty.id, { tag: "error" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!re.struct.generics) {
|
||||||
|
this.rep.error(
|
||||||
|
`struct '${re.struct.ident}' does not accept generics`,
|
||||||
|
);
|
||||||
|
this.resols.set(ty.id, { tag: "error" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (re.struct.generics.params.length !== k.args.length) {
|
||||||
|
this.rep.error(
|
||||||
|
`incorrect amount of generic paramters, expected ${re.struct.generics.params.length}, got ${k.args.length}`,
|
||||||
|
);
|
||||||
|
this.resols.set(ty.id, { tag: "error" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.resols.set(ty.id, re);
|
||||||
|
for (const arg of k.args) {
|
||||||
|
this.resolveTy(arg);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case "struct_literal": {
|
||||||
|
const fields = new Map<string, ast.Field>();
|
||||||
|
for (const field of k.fields) {
|
||||||
|
this.resolveTy(field.ty);
|
||||||
|
if (fields.has(field.ident)) {
|
||||||
|
this.rep.error(
|
||||||
|
`field '${field.ident}' already defined`,
|
||||||
|
field.loc,
|
||||||
|
);
|
||||||
|
const otherField = fields.get(field.ident)!;
|
||||||
|
this.rep.info(
|
||||||
|
`original field '${otherField.ident}' defined here`,
|
||||||
|
otherField.loc,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
fields.set(field.ident, field);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case "ident": {
|
||||||
|
const re = this.resolveIdent(k.ident);
|
||||||
|
if (re.tag === "undefined") {
|
||||||
|
this.rep.error(
|
||||||
|
`identifier '${k.ident}' is not defined`,
|
||||||
|
ty.loc,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.resols.set(ty.id, re);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case "int":
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const _: never = k;
|
||||||
|
}
|
||||||
|
|
||||||
|
private reportAlreadyDefined(ident: string, loc: ast.Loc) {
|
||||||
|
const re = this.resolveIdent(ident);
|
||||||
|
switch (re.tag) {
|
||||||
|
case "error":
|
||||||
|
case "undefined":
|
||||||
|
throw new Error();
|
||||||
|
case "struct":
|
||||||
|
this.rep.error(`identifier '${ident}' already defined`, loc);
|
||||||
|
this.rep.info(
|
||||||
|
`struct '${re.struct.ident}' defined here`,
|
||||||
|
re.struct.loc,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "generic":
|
||||||
|
this.rep.error(`identifier '${ident}' already defined`, loc);
|
||||||
|
this.rep.info(
|
||||||
|
`generic parameter '${
|
||||||
|
re.struct.generics!.params[re.idx]
|
||||||
|
}' defined here`,
|
||||||
|
re.struct.loc,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "primitive":
|
||||||
|
this.rep.error(`cannot redefine primitive '${ident}'`, loc);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static primitives = ["bool", "int", "float", "str"];
|
||||||
|
|
||||||
|
private resolveIdent(ident: string): Resol {
|
||||||
|
if (Resolver.primitives.includes(ident)) {
|
||||||
|
return { tag: "primitive" };
|
||||||
|
}
|
||||||
|
for (const rib of this.ribs.toReversed()) {
|
||||||
|
if (rib.defs.has(ident)) {
|
||||||
|
return rib.defs.get(ident)!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { tag: "undefined" };
|
||||||
|
}
|
||||||
|
|
||||||
|
private define(ident: string, re: Resol): "ok" | "already defined" {
|
||||||
|
if (this.rib().defs.has(ident)) {
|
||||||
|
return "already defined";
|
||||||
|
}
|
||||||
|
this.rib().defs.set(ident, re);
|
||||||
|
return "ok";
|
||||||
|
}
|
||||||
|
|
||||||
|
private rib(): Rib {
|
||||||
|
return this.ribs.at(-1)!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Reporter {
|
||||||
|
public errorOccured = false;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private text: string,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public error(msg: string, loc?: ast.Loc) {
|
||||||
|
this.errorOccured = true;
|
||||||
|
console.error(
|
||||||
|
`%cerror%c: ${msg}%c`,
|
||||||
|
"color: red; font-weight: bold",
|
||||||
|
"font-weight: bold",
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
if (loc) {
|
||||||
|
this.printLoc(loc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public info(msg: string, loc?: ast.Loc) {
|
||||||
|
console.error(
|
||||||
|
`%cerror%c: ${msg}%c`,
|
||||||
|
"color: cyan; font-weight: bold",
|
||||||
|
"font-weight: bold",
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
if (loc) {
|
||||||
|
this.printLoc(loc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private printLoc(loc: ast.Loc) {
|
||||||
|
const line = this.text.split("\n")[loc.start.line - 1];
|
||||||
|
const posPad = " ".repeat(loc.start.column - 1);
|
||||||
|
const lineNr = loc.start.line.toString().padStart(3, " ");
|
||||||
|
const lPad = " ".repeat(lineNr.length + 1);
|
||||||
|
const pos = `./${loc.source}:${loc.start.line}:${loc.start.column}`;
|
||||||
|
console.error(
|
||||||
|
`%c --> ${pos}\n${lPad}|\n${lineNr} |%c${line}%c\n${lPad}|${posPad}^%c`,
|
||||||
|
"color: gray",
|
||||||
|
"color: lightgray",
|
||||||
|
"color: gray",
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (import.meta.main) {
|
||||||
|
main();
|
||||||
|
}
|
12
parser.config.js
Normal file
12
parser.config.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// MJS
|
||||||
|
export default {
|
||||||
|
input: "parser.pegjs",
|
||||||
|
output: "parser.out.js",
|
||||||
|
format: "es",
|
||||||
|
sourceMap: true,
|
||||||
|
dts: true,
|
||||||
|
returnTypes: {
|
||||||
|
Ident: "string",
|
||||||
|
Int: "number",
|
||||||
|
},
|
||||||
|
};
|
60
parser.pegjs
Normal file
60
parser.pegjs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
{{
|
||||||
|
let defIds = 0;
|
||||||
|
let tyIds = 0;
|
||||||
|
|
||||||
|
const Ty = (kind, loc) => ({ id: tyIds++, loc, kind });
|
||||||
|
}}
|
||||||
|
|
||||||
|
|
||||||
|
Defs = _ defs:StructDef|.., _ | _ { return defs; }
|
||||||
|
|
||||||
|
StructDef
|
||||||
|
= "struct" _ ident:Ident _ generics:Generics? _ "{" fields:Fields "}"
|
||||||
|
{ return { id: defIds++, ident, loc: location(), generics, fields } }
|
||||||
|
|
||||||
|
Fields = _ fields:FieldDef|.., _ "," _| _ ","? _ { return fields; }
|
||||||
|
|
||||||
|
Generics = "<" _ params:Ident|.., _ "," _| _ ","? _ ">" { return { params }; }
|
||||||
|
|
||||||
|
FieldDef
|
||||||
|
= ident:Ident _ ":" _ ty:Ty
|
||||||
|
{ return { ident, loc: location(), ty }; }
|
||||||
|
|
||||||
|
Ty = ty:Ty4 { return ty; }
|
||||||
|
|
||||||
|
Ty4
|
||||||
|
= ty:Ty3 pairs:(_ "[" _ len:Ty? _ "]" { return { length: len, loc: location() }; })*
|
||||||
|
{ return pairs
|
||||||
|
.reduce((inner, {length, loc: {end}}) =>
|
||||||
|
Ty({ tag: "array", length, ty: inner }, {...inner.loc, end}),
|
||||||
|
ty); }
|
||||||
|
/ Ty3
|
||||||
|
|
||||||
|
Ty3
|
||||||
|
= ident:Ident _ "<" _ args:Ty|.., _ ", " _| _ ","? _ ">"
|
||||||
|
{ return Ty({ tag: "generic", ident, args }, location); }
|
||||||
|
/ Ty2
|
||||||
|
|
||||||
|
Ty2
|
||||||
|
= "{" fields:Fields "}"
|
||||||
|
{ return Ty({ tag: "struct_literal", fields }, location()); }
|
||||||
|
/ Ty1
|
||||||
|
|
||||||
|
Ty1 "type"
|
||||||
|
= ident:Ident { return Ty({ tag: "ident", ident }, location()) }
|
||||||
|
/ value:Int { return Ty({ tag: "int", value }, location); }
|
||||||
|
|
||||||
|
Ident "identifier"
|
||||||
|
= [a-zA-Z_][a-zA-Z0-9_]* { return text(); }
|
||||||
|
|
||||||
|
Int "integer"
|
||||||
|
= ("0" / [1-9][0-9]*) { return parseInt(text()); }
|
||||||
|
|
||||||
|
_ "whitespace"
|
||||||
|
= (WhiteSpaceChars / SingleLineComment)*
|
||||||
|
|
||||||
|
WhiteSpaceChars = [ \t\n\r]
|
||||||
|
SingleLineComment = "//" (!"\n" .)*
|
||||||
|
|
||||||
|
// vim: syntax=typescript commentstring=//\ %s
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user