From af9e8d84b9454f70255f84812b2f4bb49e6b4f8e Mon Sep 17 00:00:00 2001 From: Rob Simmons Date: Thu, 28 Nov 2024 19:43:02 -0500 Subject: [PATCH 1/6] Interface and doc refactor --- docs/astro.config.mjs | 2 + docs/src/components/Version.astro | 4 + docs/src/content/docs/docs/api/dusa.md | 41 ++++++---- .../src/content/docs/docs/api/dusasolution.md | 11 +++ docs/src/content/docs/docs/api/helpers.md | 54 +++++++++++++ docs/src/content/docs/docs/api/importing.md | 75 +++++++++++++++++++ docs/src/content/docs/docs/api/terms.md | 20 ++++- package-lock.json | 4 +- package.json | 6 +- src/bytecode.ts | 1 + src/cli.ts | 4 +- src/client.test.ts | 4 +- src/client.ts | 50 ++----------- src/compile.ts | 39 ++++++++++ src/engine/choiceengine.test.ts | 4 +- src/engine/engine.test.ts | 4 +- src/global.ts | 8 +- src/termoutput.ts | 2 +- src/web/Inspector.tsx | 9 +-- src/web/worker.ts | 5 +- 20 files changed, 264 insertions(+), 83 deletions(-) create mode 100644 docs/src/components/Version.astro create mode 100644 docs/src/content/docs/docs/api/helpers.md create mode 100644 docs/src/content/docs/docs/api/importing.md create mode 100644 src/compile.ts diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index d725682..0e6b90a 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -64,6 +64,8 @@ export default defineConfig({ { label: 'class Dusa', link: '/docs/api/dusa/' }, { label: 'class DusaSolution', link: '/docs/api/dusasolution/' }, { label: 'Terms', link: '/docs/api/terms/' }, + { label: 'Helpers', link: '/docs/api/helpers/' }, + { label: 'Using Dusa in JS', link: '/docs/api/importing/' }, ], }, ], diff --git a/docs/src/components/Version.astro b/docs/src/components/Version.astro new file mode 100644 index 0000000..9b6cb82 --- /dev/null +++ b/docs/src/components/Version.astro @@ -0,0 +1,4 @@ +--- +import * as pkg from '../../../package.json'; +--- + diff --git a/docs/src/content/docs/docs/api/dusa.md b/docs/src/content/docs/docs/api/dusa.md index c6947c7..56def82 100644 --- a/docs/src/content/docs/docs/api/dusa.md +++ b/docs/src/content/docs/docs/api/dusa.md @@ -2,9 +2,7 @@ title: class Dusa --- -The main entrypoint to the Dusa JavaScript API is the Dusa class. (The -[dusa NPM package](https://www.npmjs.com/package/dusa) also includes Typescript -definitions.) +The main entrypoint to the Dusa JavaScript API is the Dusa class. ## Creating a Dusa instance @@ -21,10 +19,10 @@ const dusa = new Dusa(` path X Z :- edge X Y, path Y Z.`); ``` -If the program has errors, an error in the `DusaError` class will be thrown. +If the program has errors, an error in the `DusaCompileError` class will be thrown. ```javascript -// raises DusaError, X is conclusion but not premise. +// raises DusaCompileError, X is conclusion but not premise. const dusa = new Dusa(`edge a X.`); ``` @@ -34,12 +32,14 @@ Dusa programs can't be directly queried: they must first be solved. There are several different ways to direct Dusa to generate solutions, all of which provide access to [`DusaSolution` objects](/docs/api/dusasolution/). -### `solution` getter +### `sample()` method and `solution` getter -Often all you need is to find a single solution (or to know that know -solutions exist). The first time you try to access `dusa.solution` some -computation will happen (and this could even fail to terminate). But then the -result is cached; subsequent calls will not trigger additional computation. +Often all you need is to find a single solution (or to know that at least one +solution exists). The `sample()` method just returns a single solution, but +will potentially return a different solution every time it is called. The +`solution` getter will generate a sample the first time it is accessed and +will then remember that sample; from then on accessing `dusa.solution` will +always return the _same_ solution until new facts are asserted. ```javascript const dusa = new Dusa(` @@ -71,14 +71,14 @@ and will always return that one. ```javascript const dusa = new Dusa(`name is { "one", "two" }.`); -dusa.solution; // raises DusaError +dusa.solution.get("name"); // either "one" or "two" ``` [Explore this example on val.town](https://www.val.town/v/robsimmons/solution_getter_maybe) -### Getting all solutions with `solve()` +### Getting a solution iterator with `solve()` -The `solve()` function returns a standard +The `solve()` function returns a extended [JavaScript iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator) that will, upon successive calls to `next()`, return each solution for the Dusa program. The iterator works in an arbitrary order: this program will @@ -95,6 +95,21 @@ console.log(iterator.next().value?.get('name')); // undefined [Explore this example on val.town](https://www.val.town/v/robsimmons/solutions_with_next) +The iterator returned by `solve` has a couple of extra methods. Trying to +return the next solution is a process that could run forever; the `advance()` +method takes an optional argument `limit` and then will run at most `limit` +steps, returning `true` if `next()` can return without any extra computation. +The `stats()` method reports how much work has been done by the iterator so +far, and `all()` returns all remaining solutions as an array. + +```javascript +advance(limit?: number): boolean; +stats(): { deductions: number; rejected: number; choices: number; nonPos: number }; +all(): DusaSolution[]; +``` + +### Using `for...of` loops + Dusa classes themselves are also `Iterable` — they implement the `[Symbol.iterator]` method and so can be used in `for..of` loops: diff --git a/docs/src/content/docs/docs/api/dusasolution.md b/docs/src/content/docs/docs/api/dusasolution.md index a6dc951..a12f411 100644 --- a/docs/src/content/docs/docs/api/dusasolution.md +++ b/docs/src/content/docs/docs/api/dusasolution.md @@ -26,6 +26,7 @@ proposition. ```typescript get(name: string, ...args: InputTerm): undefined | Term; +getBig(name: string, ...args: InputTerm): undefined | BigTerm; ``` An error will be raised if the number of `args` is not equal to the number of @@ -36,6 +37,11 @@ predicate `name` is a Datalog predicate that does not have an `is` value. ### `lookup()` method +```typescript +lookup(name: string, ...args: InputTerm): Generator; +lookupBig(name: string, ...args: InputTerm): Generator; +``` + The `lookup()` method on solutions is a powerful query mechanism. If your program has a relational proposition `path _ _`, then given only the first argument `'path'`, `lookup()` will return an iterator over all the paths. @@ -106,6 +112,11 @@ lookup(name: 'name', arg1: InputTerm): IterableIterator<[Term]>; ### `facts()` +```typescript +facts(): Fact[]; +factsBig(): BigFact[]; +``` + The `facts` method provides a list of all the [facts](/docs/api/terms/#type-fact) in a solution. The `lookup()` method is generally going to be preferable. diff --git a/docs/src/content/docs/docs/api/helpers.md b/docs/src/content/docs/docs/api/helpers.md new file mode 100644 index 0000000..7193172 --- /dev/null +++ b/docs/src/content/docs/docs/api/helpers.md @@ -0,0 +1,54 @@ +--- +title: Dusa helpers +--- + +In addition to the [Dusa class and solution iterator](/docs/api/dusa/) and the +the [DusaSolution class](/docs/api/dusasolution/), the Dusa API includes a +couple of other utility functions. + +### `compareTerm()` and `compareTerms()` + +Provides a consistent comparison function for sorting Dusa terms or list of +Dusa terms. + +```typescript +function compareTerm(t1: Term | BigTerm, t2: Term | BigTerm): number; +function compareTerms(t: (Term | BigTerm)[], s: (Term | BigTerm)[]): number; +``` + +### `termToString()` + +Provides a consistent way of making terms into strings. If `true` is passed +as the `parens` argument, then structured terms with arguments will be +surrounded by parentheses. + +```typescript +function termToString(tm: Term | BigTerm, parens = false): string; +``` + +### `check()` and `compile()` + +The `check()` function runs just static checks on a Dusa program, and returns +a list of any issues that exist. + +```typescript +interface Issue { + type: 'Issue'; + msg: string; + severity: 'warning' | 'error'; + loc?: SourceLocation; +} +function check(source: string): Issue[] | null; +``` + +The `compile()` function transforms a Dusa source program into an +intermediate "bytecode" representation which can be passed to the Dusa +constructor instead of a source program. If there are any issues, the +`DusaCompileError` exception will be thrown. + +```typescript +interface DusaCompileError extends Error { + issues: Issue[]; +} +function compile(source: string): BytecodeProgram; +``` diff --git a/docs/src/content/docs/docs/api/importing.md b/docs/src/content/docs/docs/api/importing.md new file mode 100644 index 0000000..09198d3 --- /dev/null +++ b/docs/src/content/docs/docs/api/importing.md @@ -0,0 +1,75 @@ +--- +title: Using Dusa in your JavaScript programs +--- + +As Julia Evans describes, in 2024 +[there are basically three ways use JavaScript code in your project](https://jvns.ca/blog/2024/11/18/how-to-import-a-javascript-library/). + +1. A "classic" module that just defines a global variable +2. An ES module +3. Use a build system + +### Classic modules + +To use Dusa in your random web page, include the UMD module in a script tag in +the head of your file, for example with unpkg like this: + +```html + +``` + +or with jsdelivr like this: + +```html + +``` + +This defines the `Dusa` name, which can be used to make new Dusa classes or +access the various [helpers](/docs/api/helpers/). + +```javascript +const dusa = new Dusa('fact is { mortal socrates, man socrates }.'); +function handleClick() { + const fact = Dusa.termToString(dusa.sample().get("fact")); + document.getElementById("facts").innerText = fact; +} +``` + +* [Example 1: Glitch site](https://glitch.com/edit/#!/dusa-use-umd) +* [Example 2: p5js sketch](https://editor.p5js.org/robsimmons/sketches/xcHwiBh2H) + +### ES modules + +ES modules can be used to access the [`Dusa` class](/docs/api/dusa/) and the +[helpers](/docs/api/helpers/) in any development using ES modules, without +requiring any build system. + +```javascript +import { Dusa, termToString } from 'https://unpkg.com/dusa@0.1.6/lib/client.js'; +const dusa = new Dusa('fact is { mortal socrates, man socrates }.'); +console.log(termToString(dusa.solution.get('fact'))); +``` + +The val.town examples used elsewhere in the docs use this way of importing +Dusa. [Here's the example above on val.town](https://www.val.town/v/robsimmons/fieryPlumLeopon). + +### Build system imports + +If import `dusa` through NPM (or a similar package manager/build system), then +you can import the [core `Dusa` class](/docs/api/dusa/) as well as the +[helpers](/docs/api/helpers/) through the `'dusa'` import. + +```javascript +// example.mjs +import { Dusa, termToString } from 'dusa'; +const dusa = new Dusa('fact is { mortal socrates, man socrates }.'); +console.log(termToString(dusa.solution.get('fact'))); +``` + +```javascript +// example.cjs +const { Dusa, termToString } = require('dusa'); +const dusa = new Dusa('fact is { mortal socrates, man socrates }.'); +console.log(termToString(dusa.solution.get('fact'))); +``` + diff --git a/docs/src/content/docs/docs/api/terms.md b/docs/src/content/docs/docs/api/terms.md index 7ff908e..face45c 100644 --- a/docs/src/content/docs/docs/api/terms.md +++ b/docs/src/content/docs/docs/api/terms.md @@ -8,18 +8,25 @@ All Dusa terms have a correspondence with JavaScript types: - The trivial type `()` in Dusa corresponds to `null` in JavaScript. - The string type in Dusa corresponds to the string type in JavaScript. -- The integer and natural number types in Dusa correspond to the +- The integer and natural number types in Dusa correspond to either the + Number type or the [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) type in JavaScript. The JavaScript BigInt four is written as `4n`, not `4`. - Constants like `a`, `tt`, or `bob` in Dusa correspond to objects `{ name: 'a' }`, `{ name: 'tt' }`, or `{ name: 'bob' }` in JavaScript. -- An uninterpreted function with arguments like `h 9 "fish"` in Dusa - corresponds to an object `{ name: 'h', args: [9n, 'fish'] }` in JavaScript. +- An uninterpreted function with arguments like `h c "fish"` in Dusa + corresponds to an object `{ name: 'h', args: [{ name: 'c' }, 'fish'] }` in JavaScript. ### type `Term` ```typescript export type Term = + | null // Trivial type () + | number // Natural numbers and integers + | string // Strings + | { name: string } // Constants + | { name: string; args: [Term, ...Term[]] }; +export type BigTerm = | null // Trivial type () | bigint // Natural numbers and integers | string // Strings @@ -33,7 +40,12 @@ export type Term = export interface Fact { name: string; args: Term[]; - value: Term; + value?: Term; +} +export interface BigFact { + name: string; + args: Term[]; + value?: Term; } ``` diff --git a/package-lock.json b/package-lock.json index 7c6394b..0249d06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "dusa", - "version": "0.1.5", + "version": "0.1.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dusa", - "version": "0.1.5", + "version": "0.1.6", "license": "GPL-3.0-only", "bin": { "dusa": "dusa" diff --git a/package.json b/package.json index 3f76d9e..2310232 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "dusa", - "version": "0.1.5", + "version": "0.1.6", "type": "module", "main": "lib/client.js", "unpkg": "./dusa.umd.js", - "jsdelivr": "./dus.umd.js", + "jsdelivr": "./dusa.umd.js", "types": "lib/client.d.ts", "exports": { "require": "./lib/client.cjs", @@ -30,7 +30,7 @@ "build": "tsc && vite build", "coverage": "vitest run --coverage", "dev": "vite", - "lib": "tsc --project tsconfig.package.json && rollup lib/client.js --file lib/client.cjs && rollup lib/global.js --file dusa.umd.js --format umd --name Dusa", + "lib": "tsc --project tsconfig.package.json && rollup lib/client.js --file lib/client.cjs --format cjs && rollup lib/global.js --file dusa.umd.js --format umd --name Dusa", "lint": "eslint . --report-unused-disable-directives --max-warnings 0", "prettier": "prettier --ignore-path .prettierignore --write *.ts *.json *.html *.md **/*.ts* **/*.json **/*.css **/*.html **/*.md", "prettier:check": "prettier --ignore-path .prettierignore --check .", diff --git a/src/bytecode.ts b/src/bytecode.ts index f968393..70d1056 100644 --- a/src/bytecode.ts +++ b/src/bytecode.ts @@ -215,3 +215,4 @@ export type Const = ConstN; export type Conclusion = ConclusionN; export type Rule = RuleN; export type Program = ProgramN; + diff --git a/src/cli.ts b/src/cli.ts index 11928fc..6b1a1a4 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,7 +1,7 @@ import { compareTerms, Dusa, - DusaError, + DusaCompileError, DusaRuntimeError, InputFact, InputTerm, @@ -242,7 +242,7 @@ export function runDusaCli( try { dusa = new Dusa(file); } catch (e) { - if (e instanceof DusaError) { + if (e instanceof DusaCompileError) { err( `Error${e.issues?.length === 1 ? '' : 's'} loading Dusa program:\n${e.issues .map(({ msg, loc }) => `${loc?.start ? `Line ${loc.start.line}: ` : ''}${msg}`) diff --git a/src/client.test.ts b/src/client.test.ts index 687b3aa..3bc1830 100644 --- a/src/client.test.ts +++ b/src/client.test.ts @@ -1,5 +1,5 @@ import { test, expect } from 'vitest'; -import { compareTerms, termToString, Dusa, DusaError } from './client.js'; +import { compareTerms, termToString, Dusa, DusaCompileError } from './client.js'; function solutions(dusa: Dusa, pred: string = 'res') { const sols: string[] = []; @@ -18,7 +18,7 @@ function runForDusaError(program: string) { try { new Dusa(program); } catch (e) { - if (e instanceof DusaError) { + if (e instanceof DusaCompileError) { return e.issues.map(({ msg }) => msg); } } diff --git a/src/client.ts b/src/client.ts index e7ee685..4196d5d 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,4 +1,5 @@ import { ProgramN as BytecodeProgramN } from './bytecode.js'; +import { compile } from './compile.js'; import { Data } from './datastructures/data.js'; import { Database } from './datastructures/database.js'; import { @@ -12,11 +13,6 @@ import { } from './engine/choiceengine.js'; import { assertConclusion, createSearchState, SearchState } from './engine/forwardengine.js'; import { ingestBytecodeProgram, Program as InternalProgram } from './engine/program.js'; -import { check } from './language/check.js'; -import { compile } from './language/compile.js'; -import { parse } from './language/dusa-parser.js'; -import { Issue } from './parsing/parser.js'; -import { bytecodeToJSON } from './serialize.js'; import { BigFact, BigTerm, @@ -31,18 +27,11 @@ import { } from './termoutput.js'; import { generatorMap } from './util/polyfill.js'; -export type { ProgramN as BytecodeProgramN } from './bytecode.js'; +export type BytecodeProgram = BytecodeProgramN; export type { Issue } from './parsing/parser.js'; export type { InputFact, InputTerm, Fact, BigFact, BigTerm, Term } from './termoutput.js'; export { compareTerm, compareTerms, termToString } from './termoutput.js'; - -export class DusaError extends Error { - issues: Issue[]; - constructor(issues: Issue[]) { - super(); - this.issues = issues; - } -} +export { DusaCompileError, check, compile, compileBig } from './compile.js'; export class DusaRuntimeError extends Error { constructor(msg: string) { @@ -113,7 +102,7 @@ export class Dusa { /** * Get a single arbitrary solution for the program, or null if we can be * sure that no solutions exist. This is equivalent to calling sample() once - * and remembering the DusaSolution that it returns. + * a nd remembering the DusaSolution that it returns. */ get solution(): DusaSolution | null { if (this.cachedSolution === 'unknown') { @@ -137,19 +126,10 @@ export class Dusa { return this.solve(); } - constructor(source: string | BytecodeProgramN) { + constructor(source: string | BytecodeProgramN = '') { let bytecodeProgram: BytecodeProgramN; if (typeof source === 'string') { - const parsed = parse(source); - if (parsed.errors !== null) { - throw new DusaError(parsed.errors); - } - const { errors, arities, builtins, lazy } = check(parsed.document); - if (errors.length !== 0) { - throw new DusaError(errors); - } - - bytecodeProgram = compile(builtins, arities, lazy, parsed.document); + bytecodeProgram = compile(source); } else { bytecodeProgram = source; } @@ -219,20 +199,6 @@ export class Dusa { this.state = null; } } - - static compile(source: string): BytecodeProgramN { - const parsed = parse(source); - if (parsed.errors !== null) { - throw new DusaError(parsed.errors); - } - const { errors, arities, builtins, lazy } = check(parsed.document); - if (errors.length !== 0) { - throw new DusaError(errors); - } - - const bytecodeProgram = compile(builtins, arities, lazy, parsed.document); - return bytecodeToJSON(bytecodeProgram); - } } class DusaSolutionImpl implements DusaSolution { @@ -292,7 +258,7 @@ class DusaSolutionImpl implements DusaSolution { ); } - *lookupImpl(name: string, args: InputTerm[]): Generator { + private *lookupImpl(name: string, args: InputTerm[]): Generator { const arity = this.prog.arities[name]; if (!arity) return; const depth = (arity.value ? arity.args + 1 : arity.args) - args.length; @@ -319,7 +285,7 @@ class DusaSolutionImpl implements DusaSolution { } } - factsImpl() { + private factsImpl() { return [...Object.entries(this.prog.arities)] .sort((a, b) => (a[0] > b[0] ? 1 : a[0] < b[0] ? -1 : 0)) .map(([pred, arity]) => { diff --git a/src/compile.ts b/src/compile.ts new file mode 100644 index 0000000..c227835 --- /dev/null +++ b/src/compile.ts @@ -0,0 +1,39 @@ +import { Program, ProgramN } from './bytecode.js'; +import { Issue } from './client.js'; +import { check as checkImpl } from './language/check.js'; +import { compile as compileImpl } from './language/compile.js'; +import { parse } from './language/dusa-parser.js'; +import { bytecodeToJSON } from './serialize.js'; + +export class DusaCompileError extends Error { + issues: Issue[]; + constructor(issues: Issue[]) { + super(); + this.issues = issues; + } +} + +export function check(source: string): Issue[] | null { + const parsed = parse(source); + if (parsed.errors !== null) return parsed.errors; + const { errors } = checkImpl(parsed.document); + if (errors.length !== 0) return errors; + return null; +} + +export function compileBig(source: string): Program { + const parsed = parse(source); + if (parsed.errors !== null) { + throw new DusaCompileError(parsed.errors); + } + const { errors, arities, builtins, lazy } = checkImpl(parsed.document); + if (errors.length !== 0) { + throw new DusaCompileError(errors); + } + + return compileImpl(builtins, arities, lazy, parsed.document); +} + +export function compile(source: string): ProgramN { + return bytecodeToJSON(compileBig(source)); +} diff --git a/src/engine/choiceengine.test.ts b/src/engine/choiceengine.test.ts index 09da2e2..aadce18 100644 --- a/src/engine/choiceengine.test.ts +++ b/src/engine/choiceengine.test.ts @@ -5,10 +5,10 @@ import { AgendaMember, createSearchState } from './forwardengine.js'; import { Database } from '../datastructures/database.js'; import { List } from '../datastructures/conslist.js'; import { Data } from '../datastructures/data.js'; -import { Dusa } from '../client.js'; +import { compileBig } from '../compile.js'; function build(source: string) { - return ingestBytecodeProgram(Dusa.compile(source)); + return ingestBytecodeProgram(compileBig(source)); } function simplify( diff --git a/src/engine/engine.test.ts b/src/engine/engine.test.ts index a961789..67ad84d 100644 --- a/src/engine/engine.test.ts +++ b/src/engine/engine.test.ts @@ -2,10 +2,10 @@ import { test, expect } from 'vitest'; import { Database } from '../datastructures/database.js'; import { execute } from './choiceengine.js'; import { ingestBytecodeProgram, Program } from './program.js'; -import { Dusa } from '../client.js'; +import { compileBig } from '../compile.js'; function build(source: string) { - return ingestBytecodeProgram(Dusa.compile(source)); + return ingestBytecodeProgram(compileBig(source)); } function simplify(prog: Program, db: Database, keys: [string, number][]) { diff --git a/src/global.ts b/src/global.ts index 37f4a53..e9fdd99 100644 --- a/src/global.ts +++ b/src/global.ts @@ -2,15 +2,19 @@ import { compareTerm, compareTerms, Dusa as DusaClient, - DusaError, DusaRuntimeError, termToString, + compile, + compileBig, + DusaCompileError, } from './client.js'; export default class Dusa extends DusaClient { static termToString = termToString; static compareTerm = compareTerm; static compareTerms = compareTerms; - static DusaError = DusaError; + static compile = compile; + static compileBig = compileBig; + static DusaCompileError = DusaCompileError; static DusaRuntimeError = DusaRuntimeError; } diff --git a/src/termoutput.ts b/src/termoutput.ts index fcd3cd6..128d086 100644 --- a/src/termoutput.ts +++ b/src/termoutput.ts @@ -78,7 +78,7 @@ export function termToData(data: HashCons, t: InputTerm): Data { return data.hide({ type: 'int', value: BigInt(t) }); } -export function termToString(tm: BigTerm | Term, parens = false): string { +export function termToString(tm: Term | BigTerm, parens = false): string { if (tm === null) return '()'; if (typeof tm === 'boolean') return `bool#${tm}`; if (typeof tm === 'string') return `"${escapeString(tm)}"`; diff --git a/src/web/Inspector.tsx b/src/web/Inspector.tsx index a01ff15..5608b0b 100644 --- a/src/web/Inspector.tsx +++ b/src/web/Inspector.tsx @@ -1,7 +1,7 @@ import { DOCUMENT } from 'sketchzone'; import React from 'react'; import type { WorkerStats, AppToWorkerMsg, WorkerToAppMsg } from './worker.js'; -import { Dusa, DusaError, type Issue } from '../client.js'; +import { BytecodeProgram, compile, DusaCompileError, type Issue } from '../client.js'; import type { BigFact, BigTerm } from '../termoutput.js'; import { ChevronLeftIcon, @@ -12,7 +12,6 @@ import { PlayIcon, } from '@radix-ui/react-icons'; import { escapeString } from '../datastructures/data.js'; -import { ProgramN } from '../bytecode.js'; interface Props { doc: DOCUMENT; @@ -81,11 +80,11 @@ export default function Inspector({ doc, visible }: Props) { const [state, setState] = React.useState<'running' | 'paused' | 'done'>('running'); React.useEffect(() => { - let bytecode: ProgramN; + let bytecode: BytecodeProgram; try { - bytecode = Dusa.compile(doc); + bytecode = compile(doc); } catch (e) { - if (e instanceof DusaError) { + if (e instanceof DusaCompileError) { setIssues(e.issues); return; } else { diff --git a/src/web/worker.ts b/src/web/worker.ts index 6779af9..da9fdc7 100644 --- a/src/web/worker.ts +++ b/src/web/worker.ts @@ -1,5 +1,4 @@ -import { ProgramN } from '../bytecode.js'; -import { Dusa, DusaIterator, BigFact as OutputFact } from '../client.js'; +import { Dusa, DusaIterator, BigFact as OutputFact, BytecodeProgram } from '../client.js'; export type WorkerQuery = { type: 'list'; @@ -20,7 +19,7 @@ export type WorkerToAppMsg = | { type: 'done' }; export type AppToWorkerMsg = - | { type: 'load'; program: ProgramN } + | { type: 'load'; program: BytecodeProgram } | { type: 'stop' } | { type: 'start' }; From ee8e7023004f3a9643184ae5b07d42ec3d107a08 Mon Sep 17 00:00:00 2001 From: Rob Simmons Date: Thu, 28 Nov 2024 19:46:39 -0500 Subject: [PATCH 2/6] Remover version, refactor inspector to just use client --- docs/src/components/Version.astro | 4 ---- src/web/Inspector.tsx | 10 ++++++++-- 2 files changed, 8 insertions(+), 6 deletions(-) delete mode 100644 docs/src/components/Version.astro diff --git a/docs/src/components/Version.astro b/docs/src/components/Version.astro deleted file mode 100644 index 9b6cb82..0000000 --- a/docs/src/components/Version.astro +++ /dev/null @@ -1,4 +0,0 @@ ---- -import * as pkg from '../../../package.json'; ---- - diff --git a/src/web/Inspector.tsx b/src/web/Inspector.tsx index 5608b0b..fc793ef 100644 --- a/src/web/Inspector.tsx +++ b/src/web/Inspector.tsx @@ -1,8 +1,14 @@ import { DOCUMENT } from 'sketchzone'; import React from 'react'; import type { WorkerStats, AppToWorkerMsg, WorkerToAppMsg } from './worker.js'; -import { BytecodeProgram, compile, DusaCompileError, type Issue } from '../client.js'; -import type { BigFact, BigTerm } from '../termoutput.js'; +import { + BytecodeProgram, + compile, + DusaCompileError, + type Issue, + type BigFact, + type BigTerm, +} from '../client.js'; import { ChevronLeftIcon, ChevronRightIcon, From 78e83d7ed7e097ee09a0d8a1fda22e7f03b26145 Mon Sep 17 00:00:00 2001 From: Rob Simmons Date: Thu, 28 Nov 2024 19:48:20 -0500 Subject: [PATCH 3/6] prettier --- docs/src/content/docs/docs/api/dusa.md | 6 +++--- docs/src/content/docs/docs/api/importing.md | 11 +++++------ docs/src/content/docs/docs/api/terms.md | 2 +- src/bytecode.ts | 1 - 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/docs/src/content/docs/docs/api/dusa.md b/docs/src/content/docs/docs/api/dusa.md index 56def82..593e650 100644 --- a/docs/src/content/docs/docs/api/dusa.md +++ b/docs/src/content/docs/docs/api/dusa.md @@ -37,8 +37,8 @@ provide access to [`DusaSolution` objects](/docs/api/dusasolution/). Often all you need is to find a single solution (or to know that at least one solution exists). The `sample()` method just returns a single solution, but will potentially return a different solution every time it is called. The -`solution` getter will generate a sample the first time it is accessed and -will then remember that sample; from then on accessing `dusa.solution` will +`solution` getter will generate a sample the first time it is accessed and +will then remember that sample; from then on accessing `dusa.solution` will always return the _same_ solution until new facts are asserted. ```javascript @@ -71,7 +71,7 @@ and will always return that one. ```javascript const dusa = new Dusa(`name is { "one", "two" }.`); -dusa.solution.get("name"); // either "one" or "two" +dusa.solution.get('name'); // either "one" or "two" ``` [Explore this example on val.town](https://www.val.town/v/robsimmons/solution_getter_maybe) diff --git a/docs/src/content/docs/docs/api/importing.md b/docs/src/content/docs/docs/api/importing.md index 09198d3..9a9039e 100644 --- a/docs/src/content/docs/docs/api/importing.md +++ b/docs/src/content/docs/docs/api/importing.md @@ -2,7 +2,7 @@ title: Using Dusa in your JavaScript programs --- -As Julia Evans describes, in 2024 +As Julia Evans describes, in 2024 [there are basically three ways use JavaScript code in your project](https://jvns.ca/blog/2024/11/18/how-to-import-a-javascript-library/). 1. A "classic" module that just defines a global variable @@ -30,13 +30,13 @@ access the various [helpers](/docs/api/helpers/). ```javascript const dusa = new Dusa('fact is { mortal socrates, man socrates }.'); function handleClick() { - const fact = Dusa.termToString(dusa.sample().get("fact")); - document.getElementById("facts").innerText = fact; + const fact = Dusa.termToString(dusa.sample().get('fact')); + document.getElementById('facts').innerText = fact; } ``` -* [Example 1: Glitch site](https://glitch.com/edit/#!/dusa-use-umd) -* [Example 2: p5js sketch](https://editor.p5js.org/robsimmons/sketches/xcHwiBh2H) +- [Example 1: Glitch site](https://glitch.com/edit/#!/dusa-use-umd) +- [Example 2: p5js sketch](https://editor.p5js.org/robsimmons/sketches/xcHwiBh2H) ### ES modules @@ -72,4 +72,3 @@ const { Dusa, termToString } = require('dusa'); const dusa = new Dusa('fact is { mortal socrates, man socrates }.'); console.log(termToString(dusa.solution.get('fact'))); ``` - diff --git a/docs/src/content/docs/docs/api/terms.md b/docs/src/content/docs/docs/api/terms.md index face45c..df51d12 100644 --- a/docs/src/content/docs/docs/api/terms.md +++ b/docs/src/content/docs/docs/api/terms.md @@ -8,7 +8,7 @@ All Dusa terms have a correspondence with JavaScript types: - The trivial type `()` in Dusa corresponds to `null` in JavaScript. - The string type in Dusa corresponds to the string type in JavaScript. -- The integer and natural number types in Dusa correspond to either the +- The integer and natural number types in Dusa correspond to either the Number type or the [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) type in JavaScript. The JavaScript BigInt four is written as `4n`, not `4`. diff --git a/src/bytecode.ts b/src/bytecode.ts index 70d1056..f968393 100644 --- a/src/bytecode.ts +++ b/src/bytecode.ts @@ -215,4 +215,3 @@ export type Const = ConstN; export type Conclusion = ConclusionN; export type Rule = RuleN; export type Program = ProgramN; - From 0964f468429826681bc98e2fe44e78f5aa7256c9 Mon Sep 17 00:00:00 2001 From: Rob Simmons Date: Thu, 28 Nov 2024 20:36:34 -0500 Subject: [PATCH 4/6] Fix forwardengine test, add checks for other npm versions --- .github/workflows/check.yml | 2 +- .github/workflows/check18.yml | 30 ++++++++++++++++++++++++++++++ .github/workflows/check20.yml | 30 ++++++++++++++++++++++++++++++ src/engine/forwardengine.test.ts | 4 ++-- 4 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/check18.yml create mode 100644 .github/workflows/check20.yml diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 5b5ab86..d7b13ed 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -13,7 +13,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v4 with: - node-version: 18.x + node-version: 22.x registry-url: 'https://registry.npmjs.org' - run: npm ci diff --git a/.github/workflows/check18.yml b/.github/workflows/check18.yml new file mode 100644 index 0000000..a9da72c --- /dev/null +++ b/.github/workflows/check18.yml @@ -0,0 +1,30 @@ +name: Dusa tests (node v18) + +on: + push: + +jobs: + run-static-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 18.x + registry-url: 'https://registry.npmjs.org' + - run: npm ci + + - name: Check formatting with prettier + run: npm run prettier:check + + - name: Check for issues with eslint + run: npm run lint + + - name: Check for type errors with typescript + run: npm run tsc + + - name: Run unit tests + run: npm run test diff --git a/.github/workflows/check20.yml b/.github/workflows/check20.yml new file mode 100644 index 0000000..927a812 --- /dev/null +++ b/.github/workflows/check20.yml @@ -0,0 +1,30 @@ +name: Dusa tests (npm v20) + +on: + push: + +jobs: + run-static-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + registry-url: 'https://registry.npmjs.org' + - run: npm ci + + - name: Check formatting with prettier + run: npm run prettier:check + + - name: Check for issues with eslint + run: npm run lint + + - name: Check for type errors with typescript + run: npm run tsc + + - name: Run unit tests + run: npm run test diff --git a/src/engine/forwardengine.test.ts b/src/engine/forwardengine.test.ts index 460e1c3..e68d60e 100644 --- a/src/engine/forwardengine.test.ts +++ b/src/engine/forwardengine.test.ts @@ -2,10 +2,10 @@ import { test, expect } from 'vitest'; import { createSearchState, learnImmediateConsequences, SearchState } from './forwardengine.js'; import { HashCons } from '../datastructures/data.js'; import { ingestBytecodeProgram, Program } from './program.js'; -import { Dusa } from '../client.js'; +import { compile } from '../client.js'; function build(source: string) { - return ingestBytecodeProgram(Dusa.compile(source)); + return ingestBytecodeProgram(compile(source)); } function step(prog: Program, state: SearchState) { From 41568af28702f2a36d45219a29c308e6bcca7d2e Mon Sep 17 00:00:00 2001 From: Rob Simmons Date: Thu, 28 Nov 2024 20:38:44 -0500 Subject: [PATCH 5/6] Rename tests --- .github/workflows/check18.yml | 2 +- .github/workflows/check20.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check18.yml b/.github/workflows/check18.yml index a9da72c..f74f830 100644 --- a/.github/workflows/check18.yml +++ b/.github/workflows/check18.yml @@ -4,7 +4,7 @@ on: push: jobs: - run-static-tests: + run-static-tests-node-18: runs-on: ubuntu-latest steps: - name: Checkout diff --git a/.github/workflows/check20.yml b/.github/workflows/check20.yml index 927a812..628a07e 100644 --- a/.github/workflows/check20.yml +++ b/.github/workflows/check20.yml @@ -4,7 +4,7 @@ on: push: jobs: - run-static-tests: + run-static-tests-node-20: runs-on: ubuntu-latest steps: - name: Checkout From 13a78e1d0d74ac8650e513f7e1d048b8e990a27c Mon Sep 17 00:00:00 2001 From: Rob Simmons Date: Thu, 28 Nov 2024 20:39:06 -0500 Subject: [PATCH 6/6] Node versus npm --- .github/workflows/check20.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check20.yml b/.github/workflows/check20.yml index 628a07e..39a7fd5 100644 --- a/.github/workflows/check20.yml +++ b/.github/workflows/check20.yml @@ -1,4 +1,4 @@ -name: Dusa tests (npm v20) +name: Dusa tests (node v20) on: push: