Skip to content

Commit

Permalink
Interface and doc refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
robsimmons committed Nov 29, 2024
1 parent 7225823 commit af9e8d8
Show file tree
Hide file tree
Showing 20 changed files with 264 additions and 83 deletions.
2 changes: 2 additions & 0 deletions docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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/' },
],
},
],
Expand Down
4 changes: 4 additions & 0 deletions docs/src/components/Version.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
import * as pkg from '../../../package.json';
---

41 changes: 28 additions & 13 deletions docs/src/content/docs/docs/api/dusa.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.`);
```

Expand All @@ -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(`
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down
11 changes: 11 additions & 0 deletions docs/src/content/docs/docs/api/dusasolution.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<Term[]>;
lookupBig(name: string, ...args: InputTerm): Generator<BigTerm[]>;
```

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.
Expand Down Expand Up @@ -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.
Expand Down
54 changes: 54 additions & 0 deletions docs/src/content/docs/docs/api/helpers.md
Original file line number Diff line number Diff line change
@@ -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;
```
75 changes: 75 additions & 0 deletions docs/src/content/docs/docs/api/importing.md
Original file line number Diff line number Diff line change
@@ -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
<script src="https://unpkg.com/dusa@0.1.6"></script>
```

or with jsdelivr like this:

```html
<script src="https://cdn.jsdelivr.net/npm/dusa@0.1.6"></script>
```

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')));
```

20 changes: 16 additions & 4 deletions docs/src/content/docs/docs/api/terms.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
}
```

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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 .",
Expand Down
1 change: 1 addition & 0 deletions src/bytecode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,4 @@ export type Const = ConstN<bigint>;
export type Conclusion = ConclusionN<bigint>;
export type Rule = RuleN<bigint>;
export type Program = ProgramN<bigint>;

4 changes: 2 additions & 2 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
compareTerms,
Dusa,
DusaError,
DusaCompileError,
DusaRuntimeError,
InputFact,
InputTerm,
Expand Down Expand Up @@ -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}`)
Expand Down
4 changes: 2 additions & 2 deletions src/client.test.ts
Original file line number Diff line number Diff line change
@@ -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[] = [];
Expand All @@ -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);
}
}
Expand Down
Loading

0 comments on commit af9e8d8

Please sign in to comment.