From adb4c649da9ee3a9afc2b6a3810bd514afc94b30 Mon Sep 17 00:00:00 2001 From: Matei Adriel Date: Sun, 8 Nov 2020 22:15:58 +0200 Subject: [PATCH] feat(core): nearley parser --- .vscode/settings.json | 3 +++ config/webpack.common.js | 8 ++++++ package.json | 5 ++++ pnpm-lock.yaml | 57 +++++++++++++++++++++++++++++++++++++++ src/ast.ts | 20 ++++++++++++++ src/entry.js | 2 ++ src/lexer.ts | 58 ++++++++++++++++++++++++++++++++++++++++ src/parser.ts | 14 ++++++++++ src/syntax.ne | 42 +++++++++++++++++++++++++++++ 9 files changed, 209 insertions(+) create mode 100644 src/ast.ts create mode 100644 src/lexer.ts create mode 100644 src/parser.ts create mode 100644 src/syntax.ne diff --git a/.vscode/settings.json b/.vscode/settings.json index aef4e89..d2d4e93 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,6 +13,8 @@ "hacky", "idrc", "iife", + "lparan", + "nearley", "outdir", "pcss", "peaceiris", @@ -22,6 +24,7 @@ "prebuild", "purescript", "purs", + "rparan", "spago", "thomashoneyman" ], diff --git a/config/webpack.common.js b/config/webpack.common.js index 18e409d..77c3468 100644 --- a/config/webpack.common.js +++ b/config/webpack.common.js @@ -43,6 +43,14 @@ module.exports = { options: { loader: "ts" } + }, + { + test: /\.ne$/, + use: [ + { + loader: "nearley-loader" + } + ] } ] }, diff --git a/package.json b/package.json index e58f507..f682c19 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ "devDependencies": { "@semantic-release/changelog": "^5.0.1", "@semantic-release/git": "^9.0.0", + "@types/moo": "^0.5.3", + "@types/nearley": "^2.11.1", "autoprefixer": "^10.0.1", "clean-webpack-plugin": "^3.0.0", "compression-webpack-plugin": "^6.0.5", @@ -22,6 +24,8 @@ "esbuild-loader": "^2.4.0", "html-webpack-plugin": "^4.5.0", "mini-css-extract-plugin": "^1.2.1", + "nearley": "^2.19.7", + "nearley-loader": "^2.0.0", "optimize-css-assets-webpack-plugin": "^5.0.4", "postcss": "^8.1.1", "postcss-font-magician": "^2.3.1", @@ -42,6 +46,7 @@ "@thi.ng/hiccup-canvas": "^1.1.10", "@thi.ng/vectors": "^4.7.0", "fibers": "^5.0.0", + "moo": "^0.5.1", "preact": "^10.5.5", "sass": "^1.29.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 648ae9f..9a33090 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3,11 +3,14 @@ dependencies: '@thi.ng/hiccup-canvas': 1.1.10 '@thi.ng/vectors': 4.7.0 fibers: 5.0.0 + moo: 0.5.1 preact: 10.5.5 sass: 1.29.0 devDependencies: '@semantic-release/changelog': 5.0.1_semantic-release@17.1.2 '@semantic-release/git': 9.0.0_semantic-release@17.1.2 + '@types/moo': 0.5.3 + '@types/nearley': 2.11.1 autoprefixer: 10.0.1_postcss@8.1.1 clean-webpack-plugin: 3.0.0_webpack@5.4.0 compression-webpack-plugin: 6.0.5_webpack@5.4.0 @@ -18,6 +21,8 @@ devDependencies: esbuild-loader: 2.4.0_webpack@5.4.0 html-webpack-plugin: 4.5.0_webpack@5.4.0 mini-css-extract-plugin: 1.2.1_webpack@5.4.0 + nearley: 2.19.7 + nearley-loader: 2.0.0_nearley@2.19.7 optimize-css-assets-webpack-plugin: 5.0.4_webpack@5.4.0 postcss: 8.1.1 postcss-font-magician: 2.3.1 @@ -667,6 +672,14 @@ packages: dev: true resolution: integrity: sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= + /@types/moo/0.5.3: + dev: true + resolution: + integrity: sha512-PJJ/jvb5Gor8DWvXN3e75njfQyYNRz0PaFSZ3br9GfHM9N2FxvuJ/E/ytcQePJOLzHlvgFSsIJIvfUMUxWTbnA== + /@types/nearley/2.11.1: + dev: true + resolution: + integrity: sha512-oaAg5gn74VFpPYs6Ou2pjDao3WJxnlnH29q9rLOxSGb0PTw2QtBQcTAN9xs1OAHrtI9En5kIXKM96stf7//c9w== /@types/node/14.11.2: dev: true resolution: @@ -2362,6 +2375,10 @@ packages: dev: true resolution: integrity: sha512-/d3kxZmVS+2v774mZ9SoU7H93TsAvQNpf4s/guQTva3pQSxtwUWN9dSF+Zls7sfA8ybReuR92SQMhxxUTSIGvA== + /discontinuous-range/1.0.0: + dev: true + resolution: + integrity: sha1-44Mx8IRLukm5qctxx3FYWqsbxlo= /dns-equal/1.0.0: dev: true resolution: @@ -4553,6 +4570,9 @@ packages: node: '>=0.10.0' resolution: integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== + /moo/0.5.1: + resolution: + integrity: sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w== /ms/2.0.0: dev: true resolution: @@ -4607,6 +4627,25 @@ packages: node: '>=0.10.0' resolution: integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + /nearley-loader/2.0.0_nearley@2.19.7: + dependencies: + nearley: 2.19.7 + dev: true + peerDependencies: + nearley: ^2.0.0 + resolution: + integrity: sha512-kkUlMrkLWMoQPlOVmv7a8aHFEiTOShPb1H+CkvJrDMYpMCqnQUpfJgViGFlh0wufMQlGcawPzebcng6KnDJEdg== + /nearley/2.19.7: + dependencies: + commander: 2.20.3 + moo: 0.5.1 + railroad-diagrams: 1.0.0 + randexp: 0.4.6 + semver: 5.7.1 + dev: true + hasBin: true + resolution: + integrity: sha512-Y+KNwhBPcSJKeyQCFjn8B/MIe+DDlhaaDgjVldhy5xtFewIbiQgcbZV8k2gCVwkI1ZsKCnjIYZbR+0Fim5QYgg== /negotiator/0.6.2: dev: true engines: @@ -6122,6 +6161,19 @@ packages: node: '>=8' resolution: integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== + /railroad-diagrams/1.0.0: + dev: true + resolution: + integrity: sha1-635iZ1SN3t+4mcG5Dlc3RVnN234= + /randexp/0.4.6: + dependencies: + discontinuous-range: 1.0.0 + ret: 0.1.15 + dev: true + engines: + node: '>=0.12' + resolution: + integrity: sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ== /randombytes/2.1.0: dependencies: safe-buffer: 5.2.1 @@ -7884,6 +7936,8 @@ specifiers: '@thi.ng/geom': ^1.13.0 '@thi.ng/hiccup-canvas': ^1.1.10 '@thi.ng/vectors': ^4.7.0 + '@types/moo': ^0.5.3 + '@types/nearley': ^2.11.1 autoprefixer: ^10.0.1 clean-webpack-plugin: ^3.0.0 compression-webpack-plugin: ^6.0.5 @@ -7895,6 +7949,9 @@ specifiers: fibers: ^5.0.0 html-webpack-plugin: ^4.5.0 mini-css-extract-plugin: ^1.2.1 + moo: ^0.5.1 + nearley: ^2.19.7 + nearley-loader: ^2.0.0 optimize-css-assets-webpack-plugin: ^5.0.4 postcss: ^8.1.1 postcss-font-magician: ^2.3.1 diff --git a/src/ast.ts b/src/ast.ts new file mode 100644 index 0000000..ba89ed6 --- /dev/null +++ b/src/ast.ts @@ -0,0 +1,20 @@ +// TODO: make this include position data. +type TermInput = { + application: (left: T, right: T) => T + abstraction: (name: string, body: T) => T + variable: (name: string) => T +} + +type Term = (input: TermInput) => T + +export const call = (left: Term, right: Term): Term => (config) => + config.application(left(config), right(config)) + +export const abstract = (name: string, body: Term): Term => (config) => + config.abstraction(name, body(config)) + +export const variable = (name: string): Term => (config) => + config.variable(name) + +export const abstractMany = (names: string[], body: Term): Term => + names.reduceRight((previous, name) => abstract(name, previous), body) diff --git a/src/entry.js b/src/entry.js index c400a27..a462cc0 100644 --- a/src/entry.js +++ b/src/entry.js @@ -1,6 +1,8 @@ console.log("Loaded root") console.time("Loaded purescript") +import "./parser.ts" + import("purescript/Main").then(({ main }) => { console.timeEnd("Loaded purescript") diff --git a/src/lexer.ts b/src/lexer.ts new file mode 100644 index 0000000..f47b6ab --- /dev/null +++ b/src/lexer.ts @@ -0,0 +1,58 @@ +import moo from "moo" + +export interface Position { + line: number + col: number +} + +export interface Token { + start: Position + end: Position + type?: string + value: string +} + +export const lexer = moo.compile({ + ws: /[ \t]+/, + nl: { match: "\n", lineBreaks: true }, + lambdaChar: ["λ", "\\"], + arrowChar: ["->", "."], + lparan: "(", + rparan: ")", + identifier: { + match: /[a-z_][a-z_0-9]*/ + } +}) + +export function tokenStart(token: moo.Token): Position { + return { + line: token.line, + col: token.col - 1 + } +} + +export function tokenEnd(token: moo.Token): Position { + const lastNewLine = token.text.lastIndexOf("\n") + + if (lastNewLine !== -1) { + throw new Error("Unsupported case: token with line breaks") + } + + return { + line: token.line, + col: token.col + token.text.length - 1 + } +} + +export function convertToken(token: moo.Token): Token { + return { + type: token.type, + value: token.value, + start: tokenStart(token), + end: tokenEnd(token) + } +} + +export function convertTokenId(data: moo.Token[]): Token { + return convertToken(data[0]) +} diff --git a/src/parser.ts b/src/parser.ts new file mode 100644 index 0000000..606968b --- /dev/null +++ b/src/parser.ts @@ -0,0 +1,14 @@ +// @ts-ignore +import syntax from "./syntax.ne" +import { Parser, Grammar } from "nearley" + +const parser = new Parser(Grammar.fromCompiled(syntax)) + +parser.feed("\\f a b. f b a \\l. l") +const result = parser.results[0]({ + variable: (d) => d, + abstraction: (name, body) => ({ name, body }), + application: (left, right) => ({ left, right }) +}) + +console.log(result) diff --git a/src/syntax.ne b/src/syntax.ne new file mode 100644 index 0000000..82095b5 --- /dev/null +++ b/src/syntax.ne @@ -0,0 +1,42 @@ +@{% +const { lexer, tokenStart, convertTokenId } = require("./lexer.ts") +const { call, abstract, variable, abstractMany } = require("./ast.ts") +%} + +@lexer lexer + +expression + -> application {% id %} + | abstraction {% id %} + +application + -> no_lambda_application {% id %} + | no_lambda_application __ abstraction + {% ([left, _, right]) => call(left, right) %} + +no_lambda_application + -> atom {% id %} + | no_lambda_application __ atom + {% ([left, _, right]) => call(left, right) %} + + +atom + -> "(" _ expression _ ")" {% d => d[2] %} + | variable {% id %} + +variable + -> identifier + {% ([d]) => variable(d.value) %} + +abstraction + -> lambdaChar lambdaArgs _ arrowChar _ expression + {% d => abstractMany(d[1].map(a => a.value), d[5]) %} + +lambdaArgs -> (_ identifier _ {% d => d[1] %}):+ {% id %} + +# Lexing +arrowChar -> %arrowChar {% convertTokenId %} +lambdaChar -> %lambdaChar {% convertTokenId %} +identifier -> %identifier {% convertTokenId %} +__ -> %ws:+ +_ -> %ws:* \ No newline at end of file