diff --git a/README.md b/README.md index 1462cb2..ee3b14e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # Dusa Language +Dusa is a logic programming language that has features of both Datalog and answer +set programming. + [![Build status](https://builds.sr.ht/~robsimmons/dusa.svg)](https://builds.sr.ht/~robsimmons/dusa?) [![Coverage Status](https://coveralls.io/repos/github/robsimmons/dusa/badge.svg?branch=main)](https://coveralls.io/github/robsimmons/dusa?branch=main) [![NPM Module](https://img.shields.io/npm/v/dusa.svg)](https://www.npmjs.com/package/dusa) diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 0a14014..31c745d 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -40,6 +40,14 @@ export default defineConfig({ { label: 'Syntax specification', link: '/docs/language/syntax/' }, ], }, + { + label: 'JavaScript API', + items: [ + { label: 'class Dusa', link: '/docs/api/dusa/' }, + { label: 'class DusaSolution', link: '/docs/api/dusasolution/' }, + { label: 'Terms', link: '/docs/api/terms/' }, + ], + }, ], }), ], diff --git a/docs/src/content/docs/docs/api/dusa.md b/docs/src/content/docs/docs/api/dusa.md new file mode 100644 index 0000000..c65ae7f --- /dev/null +++ b/docs/src/content/docs/docs/api/dusa.md @@ -0,0 +1,123 @@ +--- +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.) + +## Creating a Dusa instance + +### `Dusa()` constructor + +A Dusa instance is created by passing a Dusa program to the constructor. + +```javascript +const dusa = new Dusa(` + edge a b. + edge b c. + edge c d. + path X Y :- edge X Y. + path X Z :- edge X Y, path Y Z.`); +``` + +If the program has errors, an error in the `DusaError` class will be thrown. + +```javascript +// raises DusaError, X is conclusion but not premise. +const dusa = new Dusa(`edge a X.`); +``` + +## Solving a Dusa instance + +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 + +If a Dusa program has at most one solution, that solution can be accessed with the +`solution` getter. The first time this method is accessed it will cause some +computation to happen (and it could even fail to terminate if there aren't finite +solutions). The result is cached, so subsequent calls will not trigger additional +computation. + +```javascript +const dusa = new Dusa(` + edge "a" "b". + edge "b" "c". + edge "c" "d". + path X Y :- edge X Y. + path X Z :- edge X Y, path Y Z.`); +dusa.solution; // Object of type DusaSolution +[...dusa.solution.lookup('path', 'a')]; // [ "b", "c", "d" ] +[...dusa.solution.lookup('path', 'd')]; // [] +``` + +If no solutions exist, the `solution` getter will return `null`. + +```javascript +const dusa = new Dusa(` + name is "one". + name is "two".`); +dusa.solution; // null +``` + +This getter can only be used if a single solution exists. Dusa will check for +additional solutions when the `solution` getter is accessed, and will throw an +exception if there are other solutions. + +```javascript +const dusa = new Dusa(`name is { "one", "two" }.`); +dusa.solution; // raises DusaError +``` + +For programs with multiple solutions, use the `sample()` method or the `solutions` +getter, which returns an iterator. + +### `sample()` method + +The `sample()` method will return an arbitrary solution to the Dusa program, or +`null` if no solution exists. + +Each call to `sample()` re-computes the program, so even if there are only a finite +(but nonzero) number of solutions, `sample()` can be called as many times as desired. + +```javascript +const dusa = new Dusa(`name is { "one", "two" }.`); + +for (let i = 0; i < 1000; i++) { + for (const { args } of dusa.sample().lookup('name')) { + console.log(args[0]); + } +} +``` + +The current Dusa interpreter does not have a well-defined probabilistic semantics for +complex programs, but the simple program above will print `"one"` about 500 times and +will print `"two"` about 500 times. + +### solutions getter + +The `solutions` getter iterates through all the possible distinct solutions of a Dusa +program. The iterator works in an arbitrary order: this program will either print +`"one"` and then `"two"` or else it will print `"two"` and then `"one"`. + +```javascript +const dusa = new Dusa(`name is { "one", "two" }.`); + +for (const solution of dusa.solutions) { + console.log([...solution.lookup('name')].args[0]); +} +``` + +Each time the `dusa.solutions` getter is accessed, an iterator is returned that +re-runs solution search, potentially returning solutions in a different order. + +## Modifying a Dusa instance + +TODO + +### assert() method + +TODO diff --git a/docs/src/content/docs/docs/api/dusasolution.md b/docs/src/content/docs/docs/api/dusasolution.md new file mode 100644 index 0000000..48ca650 --- /dev/null +++ b/docs/src/content/docs/docs/api/dusasolution.md @@ -0,0 +1,5 @@ +--- +title: class DusaSolution +--- + +TODO diff --git a/docs/src/content/docs/docs/api/terms.md b/docs/src/content/docs/docs/api/terms.md new file mode 100644 index 0000000..dbf2a1b --- /dev/null +++ b/docs/src/content/docs/docs/api/terms.md @@ -0,0 +1,5 @@ +--- +title: Terms +--- + +TODO diff --git a/package-lock.json b/package-lock.json index 5eddae6..39175f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "dusa", - "version": "0.0.3", + "version": "0.0.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dusa", - "version": "0.0.3", + "version": "0.0.4", "license": "GPL-3.0-only", "devDependencies": { "@codemirror/commands": "^6.3.0", diff --git a/package.json b/package.json index 35186bb..7b1e616 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "dusa", - "version": "0.0.3", + "version": "0.0.4", "type": "module", "main": "lib/client.js", "types": "lib/client.d.ts", - "homepage": "https://dusa.rocks/", + "homepage": "https://dusa.rocks", "repository": { "type": "git", "url": "https://git.sr.ht/~robsimmons/dusa" @@ -12,6 +12,15 @@ "bugs": { "url": "https://todo.sr.ht/~robsimmons/Dusa" }, + "keywords": [ + "asp", + "data", + "database", + "datalog", + "immutable", + "logic programming", + "persistent" + ], "license": "GPL-3.0-only", "scripts": { "build": "tsc && vite build", diff --git a/src/client.ts b/src/client.ts index 53d1e4d..985494a 100644 --- a/src/client.ts +++ b/src/client.ts @@ -16,7 +16,12 @@ import { Issue } from './parsing/parser'; export type { Issue, Stats }; export type { SourcePosition, SourceLocation } from './parsing/source-location'; -export type Term = null | bigint | string | { name: string; args?: [Term, ...Term[]] }; +export type Term = + | null // Trivial type () + | bigint // Natural numbers and integers + | string // Strings + | { name: string } // Constants + | { name: string; args: [Term, ...Term[]] }; export interface Fact { name: string; args: Term[];