Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Interface and doc refactor #83

Merged
merged 6 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
30 changes: 30 additions & 0 deletions .github/workflows/check18.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Dusa tests (node v18)

on:
push:

jobs:
run-static-tests-node-18:
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
30 changes: 30 additions & 0 deletions .github/workflows/check20.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Dusa tests (node v20)

on:
push:

jobs:
run-static-tests-node-20:
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
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
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;
```
74 changes: 74 additions & 0 deletions docs/src/content/docs/docs/api/importing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
---
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.

Loading