diff --git a/TODO.md b/TODO.md index 414fe6d..47284b0 100644 --- a/TODO.md +++ b/TODO.md @@ -1,18 +1,4 @@ -将 explicit-substitution 从 lambda 中分离出来 - -# 有待验证的假设 - -在 explicit-substitution 中实验,然后开始 cicada。 - -- 用 `assert-equal` 验证基于 `Exp` 的 definitional 等价关系。 -- 验证基于 `Exp` 的递归函数之间的 definitional 等价关系。 -- 验证基于 `Exp` 的 dependent type 类型检查器。 -- 在实现 explicit-substitution 时避免全局的 `globalFreshen`。 - -# lang1 - -[lang1] 支持 `(assert-equal)` 与 `(assert-not-equal)` -[lang1] 支持直接递归函数与相互递归函数,不能判断等价的地方就不判断。 +rename lang0 to lang # lang0 diff --git a/docs/lang1/README.md b/docs/lang1/README.md deleted file mode 100644 index fc8f7bf..0000000 --- a/docs/lang1/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Lang1 - -Interpreter of lambda calculus with explicit substitution. - -- The meaning of scheme's `(let)` can be viewed as explicit substitution. - -```scheme -(define name body) -(define (name arg ...) body) -(import name ... "./file.scm") - -(lambda (name) ret) -(let ([name exp] ...) body) -``` diff --git a/docs/lang1/examples/boolean.scm b/docs/lang1/examples/boolean.scm deleted file mode 100644 index 41a685e..0000000 --- a/docs/lang1/examples/boolean.scm +++ /dev/null @@ -1,24 +0,0 @@ -;; Boolean - -;; # True and False - -(define (true t f) t) -(define (false t f) f) - -;; # Logical connectives - -(define (if p t f) (p t f)) - -(define (and x y) (if x y false)) -(define (or x y) (if x true y)) -(define (not x) (if x false true)) - -;; ## Tests - -(and true false) false -(or true false) true -(not true) false -(not (not true)) true - -(lambda (x) (not (not true))) -(lambda (x) true) diff --git a/docs/lang1/examples/boolean.scm.out b/docs/lang1/examples/boolean.scm.out deleted file mode 100644 index 8418ce2..0000000 --- a/docs/lang1/examples/boolean.scm.out +++ /dev/null @@ -1,10 +0,0 @@ -(lambda (t f) f) -(lambda (t f) f) -(lambda (t f) t) -(lambda (t f) t) -(lambda (t f) f) -(lambda (t f) f) -(lambda (t f) t) -(lambda (t f) t) -(lambda (x t f) t) -(lambda (x t f) t) diff --git a/docs/lang1/examples/cons.scm b/docs/lang1/examples/cons.scm deleted file mode 100644 index e69de29..0000000 diff --git a/docs/lang1/examples/nat-church.scm b/docs/lang1/examples/nat-church.scm deleted file mode 100644 index 07becfb..0000000 --- a/docs/lang1/examples/nat-church.scm +++ /dev/null @@ -1,28 +0,0 @@ -;; Church Encoding of Natural Number - -(define zero (lambda (base step) base)) -(define (add1 prev) (lambda (base step) (step (prev base step)))) -(define (iter-Nat n base step) (n base step)) - -(define one (add1 zero)) -(define two (add1 one)) -(define three (add1 two)) -(define four (add1 three)) -(define five (add1 four)) -(define six (add1 five)) -(define seven (add1 six)) -(define eight (add1 seven)) -(define nine (add1 eight)) -(define ten (add1 nine)) - -(define (add m n) (iter-Nat m n add1)) - -(add two five) seven -(add three three) six - -(define (add-rosser m n) - (lambda (base step) - (iter-Nat m (iter-Nat n base step) step))) - -(add-rosser two five) seven -(add-rosser three three) six diff --git a/docs/lang1/examples/nat-church.scm.out b/docs/lang1/examples/nat-church.scm.out deleted file mode 100644 index bb1564c..0000000 --- a/docs/lang1/examples/nat-church.scm.out +++ /dev/null @@ -1,8 +0,0 @@ -(lambda (base₆ step₁₁) (step₁₁ (step₁₁ (step₁₁ (step₁₁ (step₁₁ (step₁₁ (step₁₁ base₆)))))))) -(lambda (base₁₂ step₂₃) (step₂₃ (step₂₃ (step₂₃ (step₂₃ (step₂₃ (step₂₃ (step₂₃ base₁₂)))))))) -(lambda (base₂₆ step₅₁) (step₅₁ (step₅₁ (step₅₁ (step₅₁ (step₅₁ (step₅₁ base₂₆))))))) -(lambda (base₃₀ step₅₉) (step₅₉ (step₅₉ (step₅₉ (step₅₉ (step₅₉ (step₅₉ base₃₀))))))) -(lambda (base₄₁ step₈₁) (step₈₁ (step₈₁ (step₈₁ (step₈₁ (step₈₁ (step₈₁ (step₈₁ base₄₁)))))))) -(lambda (base₄₇ step₉₃) (step₉₃ (step₉₃ (step₉₃ (step₉₃ (step₉₃ (step₉₃ (step₉₃ base₄₇)))))))) -(lambda (base₆₀ step₁₁₉) (step₁₁₉ (step₁₁₉ (step₁₁₉ (step₁₁₉ (step₁₁₉ (step₁₁₉ base₆₀))))))) -(lambda (base₆₄ step₁₂₇) (step₁₂₇ (step₁₂₇ (step₁₂₇ (step₁₂₇ (step₁₂₇ (step₁₂₇ base₆₄))))))) diff --git a/docs/lang1/tests/compose.scm b/docs/lang1/tests/compose.scm deleted file mode 100644 index 3e0f839..0000000 --- a/docs/lang1/tests/compose.scm +++ /dev/null @@ -1,3 +0,0 @@ -(define (id x) x) - -(define (compose f g x) (f (g x))) diff --git a/docs/lang1/tests/import.scm b/docs/lang1/tests/import.scm deleted file mode 100644 index e64f198..0000000 --- a/docs/lang1/tests/import.scm +++ /dev/null @@ -1,16 +0,0 @@ -(import id compose (rename compose c) "./compose.scm") - -;; (assert-equal -;; (compose -;; (compose id id) -;; (compose id id)) -;; (c (c id id) (c id id)) -;; id) - -(compose - (compose id id) - (compose id id)) - -(c (c id id) (c id id)) - -id diff --git a/docs/lang1/tests/import.scm.out b/docs/lang1/tests/import.scm.out deleted file mode 100644 index 2fff2c7..0000000 --- a/docs/lang1/tests/import.scm.out +++ /dev/null @@ -1,3 +0,0 @@ -(lambda (x₄) x₄) -(lambda (x₁₀) x₁₀) -(lambda (x) x) diff --git a/docs/lang1/tests/invalid-name.error.scm b/docs/lang1/tests/invalid-name.error.scm deleted file mode 100644 index c61f4d5..0000000 --- a/docs/lang1/tests/invalid-name.error.scm +++ /dev/null @@ -1 +0,0 @@ -(lambda (x₀) x₀) diff --git a/docs/lang1/tests/invalid-name.error.scm.err b/docs/lang1/tests/invalid-name.error.scm.err deleted file mode 100644 index b0af613..0000000 --- a/docs/lang1/tests/invalid-name.error.scm.err +++ /dev/null @@ -1,5 +0,0 @@ -[matchExp] A name should not include subscripts: x₀ - - 1 |(lambda (x₀) x₀) - 2 | - diff --git a/docs/lang1/tests/let.scm b/docs/lang1/tests/let.scm deleted file mode 100644 index a295302..0000000 --- a/docs/lang1/tests/let.scm +++ /dev/null @@ -1,2 +0,0 @@ -(let ([id (lambda (x) x)]) - (id id)) diff --git a/docs/lang1/tests/let.scm.out b/docs/lang1/tests/let.scm.out deleted file mode 100644 index ca37804..0000000 --- a/docs/lang1/tests/let.scm.out +++ /dev/null @@ -1 +0,0 @@ -(lambda (x) x) diff --git a/package.json b/package.json index 5eb2f70..e16f11f 100644 --- a/package.json +++ b/package.json @@ -15,13 +15,10 @@ "build": "tsc", "build:watch": "tsc --watch", "test:node": "node --test", - "test:lang0:tests": "test-runner snapshot './bin/lambda.js lang0' 'docs/lang0/**/*.scm' --exclude 'docs/lang0/**/*.error.scm'", - "test:lang0:tests-error": "test-runner snapshot-error './bin/lambda.js lang0' 'docs/lang0/**/*.error.scm'", + "test:lang0:tests": "test-runner snapshot './bin/lambda.js run' 'docs/lang0/**/*.scm' --exclude 'docs/lang0/**/*.error.scm'", + "test:lang0:tests-error": "test-runner snapshot-error './bin/lambda.js run' 'docs/lang0/**/*.error.scm'", "test:lang0": "npm run test:lang0:tests && npm run test:lang0:tests-error", - "test:lang1:tests": "test-runner snapshot './bin/lambda.js lang1' 'docs/lang1/**/*.scm' --exclude 'docs/lang1/**/*.error.scm'", - "test:lang1:tests-error": "test-runner snapshot-error './bin/lambda.js lang1' 'docs/lang1/**/*.error.scm'", - "test:lang1": "npm run test:lang1:tests && npm run test:lang1:tests-error", - "test": "npm run test:node && npm run test:lang0 && npm run test:lang1", + "test": "npm run test:node && npm run test:lang0", "format": "prettier src docs --write" }, "dependencies": { diff --git a/src/command-line/commands/Lang1Command.ts b/src/command-line/commands/Lang1Command.ts deleted file mode 100644 index 24a2a44..0000000 --- a/src/command-line/commands/Lang1Command.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Command, CommandRunner } from "@xieyuheng/command-line" -import { ty } from "@xieyuheng/ty" -import fs from "fs" -import Path from "path" -import { run } from "../../lang1/run/index.js" - -type Args = { file: string } -type Opts = {} - -export class Lang1Command extends Command { - name = "lang1" - - description = "Run a lang1 file" - - args = { file: ty.string() } - opts = {} - - // prettier-ignore - help(runner: CommandRunner): string { - const { blue } = this.colors - - return [ - `The ${blue(this.name)} command run a lang1 file.`, - ``, - blue(` ${runner.name} ${this.name} `), - ``, - ].join("\n") - } - - async execute(argv: Args & Opts): Promise { - const url = createURL(argv["file"]) - - try { - await run(url) - } catch (error) { - if (error instanceof Error) { - console.error(error.message) - process.exit(1) - } - - throw error - } - } -} - -function createURL(path: string): URL { - if (ty.url().isValid(path)) { - return new URL(path) - } - - if (fs.existsSync(path) && fs.lstatSync(path).isFile()) { - const fullPath = Path.resolve(path) - return new URL(`file:${fullPath}`) - } - - throw new Error(`I can not create URL from path: ${path}`) -} diff --git a/src/command-line/commands/Lang0Command.ts b/src/command-line/commands/RunCommand.ts similarity index 86% rename from src/command-line/commands/Lang0Command.ts rename to src/command-line/commands/RunCommand.ts index e12faae..b534f08 100644 --- a/src/command-line/commands/Lang0Command.ts +++ b/src/command-line/commands/RunCommand.ts @@ -7,10 +7,10 @@ import { run } from "../../lang0/run/index.js" type Args = { file: string } type Opts = {} -export class Lang0Command extends Command { - name = "lang0" +export class RunCommand extends Command { + name = "run" - description = "Run a lang0 file" + description = "Run a file" args = { file: ty.string() } opts = {} @@ -20,7 +20,7 @@ export class Lang0Command extends Command { const { blue } = this.colors return [ - `The ${blue(this.name)} command run a lang0 file.`, + `The ${blue(this.name)} command run a file.`, ``, blue(` ${runner.name} ${this.name} `), ``, diff --git a/src/command-line/commands/index.ts b/src/command-line/commands/index.ts index d05523e..310b2a3 100644 --- a/src/command-line/commands/index.ts +++ b/src/command-line/commands/index.ts @@ -1,3 +1,2 @@ export * from "@xieyuheng/command-line/lib/commands/index.js" -export * from "./Lang0Command.js" -export * from "./Lang1Command.js" +export * from "./RunCommand.js" diff --git a/src/command-line/index.ts b/src/command-line/index.ts index 1271cf9..0671657 100644 --- a/src/command-line/index.ts +++ b/src/command-line/index.ts @@ -7,10 +7,6 @@ import * as Commands from "./commands/index.js" export function createCommandRunner(): CommandRunner { return new CommandRunners.CommonCommandRunner({ defaultCommand: new Commands.CommonHelp(), - commands: [ - new Commands.Lang0Command(), - new Commands.Lang1Command(), - new Commands.CommonHelp(), - ], + commands: [new Commands.RunCommand(), new Commands.CommonHelp()], }) } diff --git a/src/lang1/definition/Definition.ts b/src/lang1/definition/Definition.ts deleted file mode 100644 index cbc1b9e..0000000 --- a/src/lang1/definition/Definition.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { type Exp } from "../exp/index.js" -import { type Mod } from "../mod/index.js" - -export type Definition = { - mod: Mod - name: string - exp: Exp -} diff --git a/src/lang1/definition/index.ts b/src/lang1/definition/index.ts deleted file mode 100644 index ff02f98..0000000 --- a/src/lang1/definition/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./Definition.js" diff --git a/src/lang1/exp/Exp.ts b/src/lang1/exp/Exp.ts deleted file mode 100644 index fb51bff..0000000 --- a/src/lang1/exp/Exp.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { type Substitution } from "../substitution/index.js" - -export type Exp = Var | Lazy | Fn | Ap | Let - -export type Var = { - "@type": "Exp" - "@kind": "Var" - name: string -} - -export function Var(name: string): Var { - return { - "@type": "Exp", - "@kind": "Var", - name, - } -} - -export type Lazy = { - "@type": "Exp" - "@kind": "Lazy" - exp: Exp - cache?: Exp -} - -export function Lazy(exp: Exp, cache?: Exp): Lazy { - return { - "@type": "Exp", - "@kind": "Lazy", - exp, - cache, - } -} - -export type Fn = { - "@type": "Exp" - "@kind": "Fn" - name: string - ret: Exp -} - -export function Fn(name: string, ret: Exp): Fn { - return { - "@type": "Exp", - "@kind": "Fn", - name, - ret, - } -} - -export type Ap = { - "@type": "Exp" - "@kind": "Ap" - target: Exp - arg: Exp -} - -export function Ap(target: Exp, arg: Exp): Ap { - return { - "@type": "Exp", - "@kind": "Ap", - target, - arg, - } -} - -export type Let = { - "@type": "Exp" - "@kind": "Let" - substitution: Substitution - body: Exp -} - -export function Let(substitution: Substitution, body: Exp): Let { - return { - "@type": "Exp", - "@kind": "Let", - substitution, - body, - } -} diff --git a/src/lang1/exp/expFreeNames.ts b/src/lang1/exp/expFreeNames.ts deleted file mode 100644 index fbc182f..0000000 --- a/src/lang1/exp/expFreeNames.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { substitutionBindings } from "../substitution/index.js" -import { type Exp } from "./index.js" - -export function expFreeNames(boundNames: Set, exp: Exp): Set { - switch (exp["@kind"]) { - case "Var": { - return boundNames.has(exp.name) ? new Set() : new Set([exp.name]) - } - - case "Lazy": { - if (exp.cache) { - return expFreeNames(boundNames, exp.cache) - } else { - return expFreeNames(boundNames, exp.exp) - } - } - - case "Fn": { - return expFreeNames(new Set([...boundNames, exp.name]), exp.ret) - } - - case "Ap": { - return new Set([ - ...expFreeNames(boundNames, exp.target), - ...expFreeNames(boundNames, exp.arg), - ]) - } - - case "Let": { - // NOTE All bindings in the substitution are independent. - const bindings = substitutionBindings(exp.substitution) - const substitutionFreeNames = bindings - .map((binding) => Array.from(expFreeNames(boundNames, binding.exp))) - .flatMap((names) => names) - return new Set([ - ...substitutionFreeNames, - ...expFreeNames( - new Set([...boundNames, ...bindings.map((binding) => binding.name)]), - exp.body, - ), - ]) - } - } -} diff --git a/src/lang1/exp/index.ts b/src/lang1/exp/index.ts deleted file mode 100644 index 7b8109a..0000000 --- a/src/lang1/exp/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./Exp.js" -export * from "./expFreeNames.js" diff --git a/src/lang1/format/formatExp.ts b/src/lang1/format/formatExp.ts deleted file mode 100644 index c05d852..0000000 --- a/src/lang1/format/formatExp.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { type Exp } from "../exp/index.js" -import { substitutionBindings, type Binding } from "../substitution/index.js" - -export function formatExp(exp: Exp): string { - switch (exp["@kind"]) { - case "Var": { - return exp.name - } - - case "Lazy": { - if (exp.cache) { - return formatExp(exp.cache) - } else { - return formatExp(exp.exp) - } - } - - case "Fn": { - const { names, ret } = formatFn([exp.name], exp.ret) - return `(lambda (${names.join(" ")}) ${ret})` - } - - case "Ap": { - const { target, args } = formatAp(exp.target, [formatExp(exp.arg)]) - return `(${target} ${args.join(" ")})` - } - - case "Let": { - const bindings = substitutionBindings(exp.substitution).map(formatBinding) - return `(let (${bindings.join(" ")}) ${formatExp(exp.body)})` - } - } -} - -function formatFn( - names: Array, - ret: Exp, -): { names: Array; ret: string } { - if (ret["@kind"] === "Fn") { - return formatFn([...names, ret.name], ret.ret) - } else { - return { names, ret: formatExp(ret) } - } -} - -function formatAp( - target: Exp, - args: Array, -): { target: string; args: Array } { - if (target["@kind"] === "Ap") { - return formatAp(target.target, [formatExp(target.arg), ...args]) - } else { - return { target: formatExp(target), args } - } -} - -function formatBinding(binding: Binding): string { - return `[${binding.name} ${formatExp(binding.exp)}]` -} diff --git a/src/lang1/format/index.ts b/src/lang1/format/index.ts deleted file mode 100644 index 9567a6e..0000000 --- a/src/lang1/format/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./formatExp.js" diff --git a/src/lang1/mod/Mod.ts b/src/lang1/mod/Mod.ts deleted file mode 100644 index 13c8339..0000000 --- a/src/lang1/mod/Mod.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { type Definition } from "../definition/index.js" -import { type Stmt } from "../stmt/index.js" - -export type Mod = { - url: URL - loadedMods: Map - definitions: Map - stmts: Array - isExecuted?: boolean -} diff --git a/src/lang1/mod/createMod.ts b/src/lang1/mod/createMod.ts deleted file mode 100644 index b7ee3cf..0000000 --- a/src/lang1/mod/createMod.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { type Mod } from "./Mod.js" - -export function createMod(options: { - url: URL - loadedMods: Map -}): Mod { - const { url, loadedMods } = options - - return { - url, - loadedMods, - definitions: new Map(), - stmts: [], - } -} diff --git a/src/lang1/mod/index.ts b/src/lang1/mod/index.ts deleted file mode 100644 index 9edc1a7..0000000 --- a/src/lang1/mod/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./Mod.js" -export * from "./createMod.js" -export * from "./modDefine.js" -export * from "./modFind.js" -export * from "./modResolve.js" diff --git a/src/lang1/mod/modDefine.ts b/src/lang1/mod/modDefine.ts deleted file mode 100644 index 41a2efa..0000000 --- a/src/lang1/mod/modDefine.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { Definition } from "../definition/Definition.js" -import type { Mod } from "./Mod.js" -import { modFind } from "./modFind.js" - -export function modDefine( - mod: Mod, - name: string, - definition: Definition, -): void { - assertNotRedefine(mod, name) - mod.definitions.set(name, definition) -} - -function assertNotRedefine(mod: Mod, name: string): void { - if (modFind(mod, name)) { - throw new Error(`I can not redefine name: ${name}`) - } -} diff --git a/src/lang1/mod/modFind.ts b/src/lang1/mod/modFind.ts deleted file mode 100644 index 2c144a0..0000000 --- a/src/lang1/mod/modFind.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { Definition } from "../definition/Definition.js" -import type { Mod } from "./Mod.js" - -export function modFind(mod: Mod, name: string): Definition | undefined { - return mod.definitions.get(name) -} diff --git a/src/lang1/mod/modResolve.ts b/src/lang1/mod/modResolve.ts deleted file mode 100644 index 5288b72..0000000 --- a/src/lang1/mod/modResolve.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { type Mod } from "./index.js" - -export function modResolve(mod: Mod, href: string): URL { - return new URL(href, mod.url) -} diff --git a/src/lang1/reduce/index.ts b/src/lang1/reduce/index.ts deleted file mode 100644 index cc5d6f9..0000000 --- a/src/lang1/reduce/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./reduce.js" diff --git a/src/lang1/reduce/lookup.ts b/src/lang1/reduce/lookup.ts deleted file mode 100644 index fa03e71..0000000 --- a/src/lang1/reduce/lookup.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { type Exp } from "../exp/index.js" -import { type Substitution } from "../substitution/index.js" - -export function lookup( - name: string, - substitution: Substitution, -): Exp | undefined { - for (const binding of substitution.values()) { - if (binding.name === name) { - return binding.exp - } - } - - return undefined -} diff --git a/src/lang1/reduce/reduce.ts b/src/lang1/reduce/reduce.ts deleted file mode 100644 index 0443a71..0000000 --- a/src/lang1/reduce/reduce.ts +++ /dev/null @@ -1,54 +0,0 @@ -import * as Exps from "../exp/index.js" -import { type Exp } from "../exp/index.js" -import { modFind, type Mod } from "../mod/index.js" -import { substitutionInitial } from "../substitution/index.js" -import { substitute } from "./substitute.js" - -// NOTE `reduce` might hit fixpoint on other kind of expressions, -// but it will always remove `Let`. - -export function reduce(mod: Mod, exp: Exp): Exp { - switch (exp["@kind"]) { - case "Var": { - const defintion = modFind(mod, exp.name) - if (defintion) { - return reduce(mod, defintion.exp) - } else { - return exp - } - } - - case "Lazy": { - if (exp.cache) { - return exp.cache - } else { - exp.cache = reduce(mod, exp.exp) - return exp.cache - } - } - - case "Fn": { - return Exps.Fn(exp.name, reduce(mod, exp.ret)) - } - - case "Ap": { - const target = reduce(mod, exp.target) - const arg = Exps.Lazy(exp.arg) - - switch (target["@kind"]) { - case "Fn": { - const substitution = substitutionInitial(target.name, arg) - return reduce(mod, substitute(substitution, target.ret)) - } - - default: { - return Exps.Ap(target, reduce(mod, arg)) - } - } - } - - case "Let": { - return reduce(mod, substitute(exp.substitution, exp.body)) - } - } -} diff --git a/src/lang1/reduce/substitute.ts b/src/lang1/reduce/substitute.ts deleted file mode 100644 index ef39a84..0000000 --- a/src/lang1/reduce/substitute.ts +++ /dev/null @@ -1,79 +0,0 @@ -import * as Exps from "../exp/index.js" -import { type Exp } from "../exp/index.js" -import { - substitutionExtend, - substitutionIsEmpty, - substitutionMapExp, - substitutionMerge, - substitutionTakeNames, - type Substitution, -} from "../substitution/index.js" -import { globalFreshen } from "../utils/globalFreshen.js" -import { lookup } from "./lookup.js" - -// NOTE `substitute` should not call `reduce. - -export function substitute(substitution: Substitution, body: Exp): Exp { - substitution = substitutionTakeNames( - substitution, - Exps.expFreeNames(new Set(), body), - ) - - if (substitutionIsEmpty(substitution)) { - return body - } - - switch (body["@kind"]) { - case "Var": { - const found = lookup(body.name, substitution) - if (found) { - return found - } else { - return body - } - } - - case "Lazy": { - if (body.cache) { - return substitute(substitution, body.cache) - } else { - return Exps.Lazy(substitute(substitution, body.exp)) - } - } - - case "Fn": { - const freshName = globalFreshen(body.name) - return Exps.Fn( - freshName, - Exps.Let( - substitutionExtend(substitution, body.name, Exps.Var(freshName)), - body.ret, - ), - ) - } - - case "Ap": { - return Exps.Ap( - Exps.Let(substitution, body.target), - Exps.Let(substitution, body.arg), - ) - } - - case "Let": { - return substitute( - composeSubstitution(substitution, body.substitution), - body.body, - ) - } - } -} - -export function composeSubstitution( - left: Substitution, - right: Substitution, -): Substitution { - return substitutionMerge( - left, - substitutionMapExp(right, (exp) => substitute(left, exp)), - ) -} diff --git a/src/lang1/run/execute.ts b/src/lang1/run/execute.ts deleted file mode 100644 index ab01d9f..0000000 --- a/src/lang1/run/execute.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { formatExp } from "../format/formatExp.js" -import { modDefine, type Mod } from "../mod/index.js" -import { reduce } from "../reduce/reduce.js" -import { type Stmt } from "../stmt/Stmt.js" -import { importOne } from "./importOne.js" - -export function execute(mod: Mod, stmt: Stmt): null | string { - switch (stmt["@kind"]) { - case "Compute": { - const reducedExp = reduce(mod, stmt.exp) - return formatExp(reducedExp) - } - - case "Define": { - modDefine(mod, stmt.name, { - mod, - name: stmt.name, - exp: stmt.exp, - }) - return null - } - - case "Import": { - for (const entry of stmt.entries) { - importOne(mod, stmt.path, entry) - } - - return null - } - } -} diff --git a/src/lang1/run/executeMod.ts b/src/lang1/run/executeMod.ts deleted file mode 100644 index 9cbd5a3..0000000 --- a/src/lang1/run/executeMod.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { Mod } from "../mod/index.js" -import { execute } from "./execute.js" - -export function executeMod(mod: Mod): boolean { - if (mod.isExecuted) { - return false - } - - for (const stmt of mod.stmts) { - const output = execute(mod, stmt) - if (output) { - console.log(output) - } - } - - mod.isExecuted = true - return true -} diff --git a/src/lang1/run/importOne.ts b/src/lang1/run/importOne.ts deleted file mode 100644 index c1f036c..0000000 --- a/src/lang1/run/importOne.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { modDefine, modFind, modResolve } from "../mod/index.js" -import type { Mod } from "../mod/Mod.js" -import type { ImportEntry } from "../stmt/Stmt.js" -import { executeMod } from "./executeMod.js" - -export function importOne(mod: Mod, path: string, entry: ImportEntry): void { - const url = modResolve(mod, path) - if (url.href === mod.url.href) { - throw new Error(`I can not circular import: ${path}`) - } - - const found = mod.loadedMods.get(url.href) - if (found === undefined) { - throw new Error(`Mod is not loaded: ${path}`) - } - - executeMod(found.mod) - - const { name, rename } = entry - - const def = modFind(found.mod, name) - if (def === undefined) { - throw new Error( - `I can not import undefined name: ${name}, from path: ${path}`, - ) - } - - modDefine(mod, rename || name, def) -} diff --git a/src/lang1/run/index.ts b/src/lang1/run/index.ts deleted file mode 100644 index 89e8e9f..0000000 --- a/src/lang1/run/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./run.js" diff --git a/src/lang1/run/load.ts b/src/lang1/run/load.ts deleted file mode 100644 index d3229f3..0000000 --- a/src/lang1/run/load.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Fetcher } from "@cicada-lang/framework/lib/fetcher/index.js" -import fs from "node:fs" -import { ParsingError } from "../../sexp/index.js" -import { createMod, modResolve, type Mod } from "../mod/index.js" -import { type Stmt } from "../stmt/index.js" -import { Parser } from "../syntax/index.js" - -const fetcher = new Fetcher() - -fetcher.register("file", (url) => fs.promises.readFile(url.pathname, "utf8")) - -export async function load( - url: URL, - loadedMods: Map, -): Promise { - const found = loadedMods.get(url.href) - if (found !== undefined) { - return found.mod - } - - const text = await fetcher.fetch(url) - - try { - const mod = createMod({ url, loadedMods }) - mod.stmts = parseStmts(text) - loadedMods.set(url.href, { mod, text }) - - for (const stmt of mod.stmts) { - if (stmt["@kind"] === "Import") { - const importedUrl = modResolve(mod, stmt.path) - await load(importedUrl, loadedMods) - } - } - - return mod - } catch (error) { - if (error instanceof ParsingError) { - throw new Error(error.report(text)) - } - - throw error - } -} - -function parseStmts(text: string): Array { - const parser = new Parser() - return parser.parseStmts(text) -} diff --git a/src/lang1/run/run.ts b/src/lang1/run/run.ts deleted file mode 100644 index 41ced45..0000000 --- a/src/lang1/run/run.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { executeMod } from "./executeMod.js" -import { load } from "./load.js" - -export async function run(url: URL): Promise { - const mod = await load(url, new Map()) - executeMod(mod) -} diff --git a/src/lang1/stmt/Stmt.ts b/src/lang1/stmt/Stmt.ts deleted file mode 100644 index 4c743c2..0000000 --- a/src/lang1/stmt/Stmt.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { type Exp } from "../exp/index.js" - -export type Stmt = Compute | Define | Import - -export type Compute = { - "@type": "Stmt" - "@kind": "Compute" - exp: Exp -} - -export function Compute(exp: Exp): Compute { - return { - "@type": "Stmt", - "@kind": "Compute", - exp, - } -} - -export type Define = { - "@type": "Stmt" - "@kind": "Define" - name: string - exp: Exp -} - -export function Define(name: string, exp: Exp): Define { - return { - "@type": "Stmt", - "@kind": "Define", - name, - exp, - } -} - -export type ImportEntry = { - name: string - rename?: string -} - -export type Import = { - "@type": "Stmt" - "@kind": "Import" - path: string - entries: Array -} - -export function Import(path: string, entries: Array): Import { - return { - "@type": "Stmt", - "@kind": "Import", - path, - entries, - } -} diff --git a/src/lang1/stmt/index.ts b/src/lang1/stmt/index.ts deleted file mode 100644 index b54cfa1..0000000 --- a/src/lang1/stmt/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./Stmt.js" diff --git a/src/lang1/substitution/Substitution.ts b/src/lang1/substitution/Substitution.ts deleted file mode 100644 index 20f20ea..0000000 --- a/src/lang1/substitution/Substitution.ts +++ /dev/null @@ -1,70 +0,0 @@ -import type { Exp } from "../exp/index.js" - -export type Binding = { - name: string - exp: Exp -} - -export type Substitution = Map - -export function substitutionIsEmpty(substitution: Substitution): boolean { - return substitution.size === 0 -} - -export function substitutionFromBindings( - bindings: Array, -): Substitution { - return new Map([ - ...bindings.map<[string, Binding]>((binding) => [binding.name, binding]), - ]) -} - -export function substitutionBindings( - substitution: Substitution, -): Array { - return Array.from(substitution.values()) -} - -export function substitutionInitial(name: string, exp: Exp): Substitution { - return new Map([[name, { name, exp }]]) -} - -export function substitutionExtend( - substitution: Substitution, - name: string, - exp: Exp, -): Substitution { - return new Map([...substitution, [name, { name, exp }]]) -} - -export function substitutionMerge( - left: Substitution, - right: Substitution, -): Substitution { - return new Map([...left, ...right]) -} - -export function substitutionMapExp( - substitution: Substitution, - f: (exp: Exp) => Exp, -): Substitution { - return new Map([ - ...Array.from(substitution.values()).map<[string, Binding]>( - ({ name, exp }) => [name, { name, exp: f(exp) }], - ), - ]) -} - -export function substitutionTakeNames( - substitution: Substitution, - names: Set, -): Substitution { - const newSubstitution = new Map() - for (const [name, exp] of substitution) { - if (names.has(name)) { - newSubstitution.set(name, exp) - } - } - - return newSubstitution -} diff --git a/src/lang1/substitution/index.ts b/src/lang1/substitution/index.ts deleted file mode 100644 index 279cb45..0000000 --- a/src/lang1/substitution/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./Substitution.js" diff --git a/src/lang1/syntax/Parser.ts b/src/lang1/syntax/Parser.ts deleted file mode 100644 index 9721668..0000000 --- a/src/lang1/syntax/Parser.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Parser as SexpParser } from "../../sexp/index.js" -import { type Stmt } from "../stmt/index.js" -import { matchStmt } from "./matchStmt.js" - -export class Parser extends SexpParser { - constructor() { - super({ - quotes: [ - { mark: "'", symbol: "quote" }, - { mark: ",", symbol: "unquote" }, - { mark: "`", symbol: "quasiquote" }, - ], - parentheses: [ - { start: "(", end: ")" }, - { start: "[", end: "]" }, - ], - comments: [";"], - }) - } - - parseStmts(text: string): Array { - return this.parseSexps(text).map(matchStmt) - } -} diff --git a/src/lang1/syntax/index.ts b/src/lang1/syntax/index.ts deleted file mode 100644 index 25fb920..0000000 --- a/src/lang1/syntax/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./Parser.js" diff --git a/src/lang1/syntax/matchExp.ts b/src/lang1/syntax/matchExp.ts deleted file mode 100644 index e37b3d2..0000000 --- a/src/lang1/syntax/matchExp.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { cons, match, matchList, v, type Sexp } from "../../sexp/index.js" -import * as Exps from "../exp/index.js" -import { type Exp } from "../exp/index.js" -import { - substitutionFromBindings, - type Binding, -} from "../substitution/index.js" -import { matchName } from "./matchName.js" - -export function matchExp(sexp: Sexp): Exp { - return match(sexp, [ - [ - ["lambda", v("names"), v("exp")], - ({ names, exp }) => - matchList(names, matchName).reduceRight( - (fn, name) => Exps.Fn(name, fn), - matchExp(exp), - ), - ], - - [ - ["let", v("bindings"), v("body")], - ({ bindings, body }) => - Exps.Let( - substitutionFromBindings(matchList(bindings, matchBinding)), - matchExp(body), - ), - ], - - [ - cons(v("target"), v("args")), - ({ target, args }) => - matchList(args, matchExp).reduce( - (result, arg) => Exps.Ap(result, arg), - matchExp(target), - ), - ], - - [v("name"), ({ name }, { span }) => Exps.Var(matchName(name))], - ]) -} - -export function matchBinding(sexp: Sexp): Binding { - return match(sexp, [ - [ - [v("name"), v("exp")], - ({ name, exp }) => ({ - name: matchName(name), - exp: matchExp(exp), - }), - ], - ]) -} diff --git a/src/lang1/syntax/matchName.ts b/src/lang1/syntax/matchName.ts deleted file mode 100644 index 830e22c..0000000 --- a/src/lang1/syntax/matchName.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ParsingError, matchSymbol, type Sexp } from "../../sexp/index.js" -import { numberSubscripts } from "../../utils/stringToSubscript.js" - -export function matchName(sexp: Sexp): string { - const nameString = matchSymbol(sexp) - const subscripts = Object.values(numberSubscripts) - if (subscripts.some((subscript) => nameString.includes(subscript))) { - throw new ParsingError( - `[matchExp] A name should not include subscripts: ${nameString}`, - sexp.span, - ) - } - - return nameString -} diff --git a/src/lang1/syntax/matchStmt.ts b/src/lang1/syntax/matchStmt.ts deleted file mode 100644 index ab64a10..0000000 --- a/src/lang1/syntax/matchStmt.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { - cons, - listToArray, - match, - matchList, - matchString, - matchSymbol, - v, - type Sexp, -} from "../../sexp/index.js" -import * as Exps from "../exp/index.js" -import * as Stmts from "../stmt/index.js" -import { type Stmt } from "../stmt/index.js" -import { matchExp } from "./matchExp.js" -import { matchName } from "./matchName.js" - -export function matchStmt(sexp: Sexp): Stmt { - return match(sexp, [ - [ - ["define", cons(v("name"), v("args")), v("exp")], - ({ name, args, exp }) => - Stmts.Define( - matchName(name), - matchList(args, matchName).reduceRight( - (fn, name) => Exps.Fn(name, fn), - matchExp(exp), - ), - ), - ], - - [ - ["define", v("name"), v("exp")], - ({ name, exp }) => Stmts.Define(matchName(name), matchExp(exp)), - ], - - [ - cons("import", v("body")), - ({ body }) => { - const sexps = listToArray(body) - const url = sexps[sexps.length - 1] - const entries = sexps.slice(0, sexps.length - 1) - return Stmts.Import(matchString(url), entries.map(matchImportEntry)) - }, - ], - - [v("exp"), ({ exp }) => Stmts.Compute(matchExp(exp))], - ]) -} - -function matchImportEntry(sexp: Sexp): Stmts.ImportEntry { - return match(sexp, [ - [ - ["rename", v("name"), v("rename")], - ({ name, rename }) => ({ - name: matchSymbol(name), - rename: matchSymbol(rename), - }), - ], - - [v("name"), ({ name }) => ({ name: matchSymbol(name) })], - ]) -} diff --git a/src/lang1/utils/globalFreshen.ts b/src/lang1/utils/globalFreshen.ts deleted file mode 100644 index 7976427..0000000 --- a/src/lang1/utils/globalFreshen.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { - numberSubscripts, - stringToSubscript, -} from "../../utils/stringToSubscript.js" - -export let globalNameCounters: Map = new Map() - -export function globalFreshen(name: string): string { - name = nameWithoutSubscript(name) - const globalNameCounter = globalNameCounters.get(name) - if (globalNameCounter === undefined) { - globalNameCounters.set(name, 1) - const subscript = stringToSubscript(String(1)) - return `${name}${subscript}` - } else { - globalNameCounters.set(name, globalNameCounter + 1) - const subscript = stringToSubscript(String(globalNameCounter + 1)) - return `${name}${subscript}` - } -} - -function nameWithoutSubscript(name: string): string { - const subscripts = Array.from(Object.values(numberSubscripts)) - const chars = name.split("") - return chars.filter((char) => !subscripts.includes(char)).join("") -}