Skip to content
Open
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
31 changes: 18 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,36 +23,41 @@ npm install penman-js

## Basic usage

The most faithful representation of AMR text in the library is the `Tree` class. The `parse` function turns an AMR text string into a `Tree`, and `format` does the reverse, turning a `Tree` back into a string.
The most faithful representation of AMR text in the library is the `Tree` class. The `Tree.fromPenman()` method turns an AMR text string into a `Tree`, and `tree.toPenman()` does the reverse, turning a `Tree` back into a string.

```js
import { parse, format } from 'penman-js';
import { Tree } from 'penman-js';

const t = penman.parse('(w / want-01 :ARG0 (b / boy) :ARG1 (g / go :ARG0 b))');
const [variable, branches] = t.node;
const tree = Tree.fromPenman(
'(w / want-01 :ARG0 (b / boy) :ARG1 (g / go :ARG0 b))',
);
const [variable, branches] = tree.node;
console.log(variable); // ouput: 'w'
console.log(branches.length); // output: 3
const [role, target] = branches[2];
console.log(role); // output: ':ARG1'
console.log(format(target));
const subtree = new Tree(target);
console.log(subtree.toPenman());
// (g / go
// :ARG0 b)
```

Users wanting to interact with graphs might find the `decode` and
`encode` functions a good place to start.
Users wanting to interact with graphs might find the `Graph.fromPenman()` and
`graph.toPenman()` methods a good place to start.

```js
import { encode, decode } from 'penman-js';
const g = penman.decode('(w / want-01 :ARG0 (b / boy) :ARG1 (g / go :ARG0 b))');
console.log(g.top);
import { Graph } from 'penman-js';
const graph = Graph.fromPenman(
'(w / want-01 :ARG0 (b / boy) :ARG1 (g / go :ARG0 b))',
);
console.log(graph.top);
// 'w'
console.log(g.triples.length);
console.log(graph.triples.length);
// 6
console.log(g.instances().map((instance) => instance[2]));
console.log(graph.instances().map((instance) => instance[2]));
// ['want-01', 'boy', 'go']

console.log(encode(g, { top: 'b' }));
console.log(graph.toPenman({ top: 'b' }));
// (b / boy
// :ARG0-of (w / want-01
// :ARG1 (g / go
Expand Down
35 changes: 18 additions & 17 deletions docs/docs/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,42 +10,43 @@ npm install penman-js

## Basic usage

The most faithful representation of AMR text in the library is the `Tree` class. The `parse` function turns an AMR text string into a `Tree`, and `format` does the reverse, turning a `Tree` back into a string.
The most faithful representation of AMR text in the library is the `Tree` class. The `Tree.fromPenman()` method turns an AMR text string into a `Tree`, and `tree.toPenman()` does the reverse, turning a `Tree` back into a string.

```js
import { parse, format } from 'penman-js';
import { Tree } from 'penman-js';

const t = penman.parse('(w / want-01 :ARG0 (b / boy) :ARG1 (g / go :ARG0 b))');
const [variable, branches] = t.node;
const tree = Tree.fromPenman(
'(w / want-01 :ARG0 (b / boy) :ARG1 (g / go :ARG0 b))',
);
const [variable, branches] = tree.node;
console.log(variable); // ouput: 'w'
console.log(branches.length); // output: 3
const [role, target] = branches[2];
console.log(role); // output: ':ARG1'
console.log(format(target));
const subtree = new Tree(target);
console.log(subtree.toPenman());
// (g / go
// :ARG0 b)
```

Users wanting to interact with graphs might find the `decode` and
`encode` functions a good place to start.
Users wanting to interact with graphs might find the `Graph.fromPenman()` and
`graph.toPenman()` methods a good place to start.

```js
import { encode, decode } from 'penman-js';
const g = penman.decode('(w / want-01 :ARG0 (b / boy) :ARG1 (g / go :ARG0 b))');
console.log(g.top);
import { Graph } from 'penman-js';
const graph = Graph.fromPenman(
'(w / want-01 :ARG0 (b / boy) :ARG1 (g / go :ARG0 b))',
);
console.log(graph.top);
// 'w'
console.log(g.triples.length);
console.log(graph.triples.length);
// 6
console.log(g.instances().map((instance) => instance[2]));
console.log(graph.instances().map((instance) => instance[2]));
// ['want-01', 'boy', 'go']

console.log(encode(g, { top: 'b' }));
console.log(graph.toPenman({ top: 'b' }));
// (b / boy
// :ARG0-of (w / want-01
// :ARG1 (g / go
// :ARG0 b)))
```

The `decode` and `encode` functions work with one PENMAN
graph. The `load` and `dump` functions work with
collections of graphs.
10 changes: 10 additions & 0 deletions docs/docs/trees-graphs-epigraphs.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ string is **formatting**, while the whole process is called
![The three stages of PENMAN structure](/img/representations.png)
</div>

These functions for moving between Penman notation, trees, and graphs are also
available in Penman JS as methods on `Graph` and `Tree` objects, as below:

- `Graph.fromPenman()`: equivalent of `decode()` above
- `graph.toPenman()`: equivalent of `encode()` above
- `graph.toTree()`: equivalent of `configure()` above
- `Tree.fromPenman()`: equivalent of `parse()` above
- `tree.toPenman()`: equivalent of `format()` above
- `tree.toGraph()`: equivalent of `interpret()` above

Conversion from a PENMAN string to a `Tree`, and
vice versa, is straightforward and lossless. Conversion to a
`Graph`, however, is potentially lossy as the
Expand Down
4 changes: 2 additions & 2 deletions src/lib/codec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ export class PENMANCodec {
*
* @param s - A string containing a single PENMAN-serialized graph.
* @param options - Optional arguments.
* - `model` - The model used for interpreting the graph.
* @param options.model - The model used for interpreting the graph.
* @returns The Graph object described by `s`.
* @example
* import { decode } from 'penman-js';
Expand All @@ -247,7 +247,7 @@ export function decode(s: string, options: DecodeOptions = {}): Graph {
*
* @param lines - A string or open file containing PENMAN-serialized graphs.
* @param options - Optional arguments.
* - `model` - The model used for interpreting the graph.
* @param options.model - The model used for interpreting the graph.
* @returns An iterator yielding `Graph` objects described in `lines`.
* @example
* import { iterdecode } from 'penman-js';
Expand Down
73 changes: 73 additions & 0 deletions src/lib/graph.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,79 @@ test('init', (t) => {
t.is(g3.top, 'b');
});

test('Graph.fromPenman', (t) => {
// unlabeled single node
let g = Graph.fromPenman('(a)');
t.is(g.top, 'a');
t.deepEqual(g.triples, [['a', ':instance', null]]);

// labeled node
g = Graph.fromPenman('(a / alpha)');
t.is(g.top, 'a');
t.deepEqual(g.triples, [['a', ':instance', 'alpha']]);

// unlabeled edge to unlabeled node
g = Graph.fromPenman('(a : (b))');
t.is(g.top, 'a');
t.deepEqual(g.triples, [
['a', ':instance', null],
['a', ':', 'b'],
['b', ':instance', null],
]);

// inverted unlabeled edge
g = Graph.fromPenman('(b :-of (a))');
t.is(g.top, 'b');
t.deepEqual(g.triples, [
['b', ':instance', null],
['a', ':', 'b'],
['a', ':instance', null],
]);

// labeled edge to unlabeled node
g = Graph.fromPenman('(a :ARG (b))');
t.is(g.top, 'a');
t.deepEqual(g.triples, [
['a', ':instance', null],
['a', ':ARG', 'b'],
['b', ':instance', null],
]);

// inverted edge
g = Graph.fromPenman('(b :ARG-of (a))');
t.is(g.top, 'b');
t.deepEqual(g.triples, [
['b', ':instance', null],
['a', ':ARG', 'b'],
['a', ':instance', null],
]);

// fuller examples
t.deepEqual(Graph.fromPenman(x1()[0]).triples, x1()[1]);
});

test('toTree', (t) => {
const g = new Graph([
['b', ':instance', 'bark-01'],
['b', ':ARG0', 'd'],
['d', ':instance', 'dog'],
]);

const tree = g.toTree();
t.deepEqual(tree.node, [
'b',
[
['/', 'bark-01'],
[':ARG0', ['d', [['/', 'dog']]]],
],
]);
});

test('toPenman', (t) => {
const g = new Graph([['h', 'instance', 'hi']]);
t.deepEqual(g.toPenman(), '(h / hi)');
});

test('__or__', (t) => {
const p = new Graph();
const g = p.__or__(p);
Expand Down
119 changes: 119 additions & 0 deletions src/lib/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ import cloneDeep from 'lodash.clonedeep';
import differenceWith from 'lodash.differencewith';
import isEqual from 'lodash.isequal';

import { decode, encode } from './codec';
import { EpidataMap } from './epigraph';
import { GraphError } from './exceptions';
import { configure } from './layout';
import { Model } from './model';
import { Tree } from './tree';
import type {
Attribute,
Constant,
Expand Down Expand Up @@ -47,6 +51,28 @@ export interface GraphEdgesOptions {
target?: Constant;
}

export interface GraphFromPenmanOptions {
/** The model used for interpreting the graph. */
model?: Model;
}

export interface GraphToTreeOptions {
/** If given, the node to use as the top in serialization. */
top?: Variable;
/** The model used for interpreting the graph. */
model?: Model;
}
export interface GraphToPenmanOptions {
/** The model used for interpreting the graph. */
model?: Model;
/** If given, the node to use as the top in serialization. */
top?: Variable;
/** How to indent formatted strings. */
indent?: number | null;
/** If `true`, put initial attributes on the first line. */
compact?: boolean;
}

/**
* Represents a basic class for modeling a rooted, directed acyclic graph.
*
Expand Down Expand Up @@ -105,6 +131,99 @@ export class Graph {
this._id = graphIdCounter++;
}

/**
* Deserialize PENMAN-serialized string `s` into its Graph object.
*
* This is equivalent to `decode()` in the Python library
*
* `options` consists of the following:
* - `model` - The model used for interpreting the graph.
*
* @param penmanString - A string containing a single PENMAN-serialized graph.
* @param options - Optional arguments.
* @param options.model - The model used for interpreting the graph.
* @returns The Graph object described by `penmanString`.
* @example
* import { Graph } from 'penman-js';
*
* const graph = Graph.fromPenman('(b / bark-01 :ARG0 (d / dog))');
*/
static fromPenman(
penmanString: string,
options: GraphFromPenmanOptions = {},
): Graph {
return decode(penmanString, options);
}

/**
* Create a tree from the graph by making as few decisions as possible.
*
* A graph created from a valid tree will
* contain epigraphical markers that describe how the triples of a
* graph are to be expressed in a tree, and thus configuring this
* tree requires only a single pass through the list of triples. If
* the markers are missing or out of order, or if the graph has been
* modified, then the process of creating the tree will have to make
* decisions about where to insert tree branches. These decisions are
* deterministic, but may result in a tree different than the one
* expected.
*
* This is equivalent to `configure()` in the Python library.
*
* `options` consists of the following:
* - `top` is the variable to use as the top of the graph; if `null`, the top of `g` will be used.
* - `model` is the `Model` used to configure the tree.
*
* @param options - Optional arguments.
* @param options.top` is the variable to use as the top of the graph; if `null`, the top of `g` will be used.
* @param options.model` is the `Model` used to configure the tree.
* @returns The `Tree` object.
* @example
* import { Graph } from 'penman-js';
*
* const g = new Graph([
* ['b', ':instance', 'bark-01'],
* ['b', ':ARG0', 'd'],
* ['d', ':instance', 'dog']
* ]);
*
* const t = g.toTree());
* console.log(t);
* // Tree(['b', [['/', 'bark-01'], [':ARG0', ['d', [['/', 'dog']]]]]])
*/
toTree(options: GraphToTreeOptions = {}): Tree {
return configure(this, options);
}

/**
* Serialize the graph from `top` to PENMAN notation.
*
* This is equivalent to `encode()` in the Python library.
*
* `options` consists of the following:
* - `top` - If given, the node to use as the top in serialization.
* - `indent` - How to indent formatted strings.
* - `compact` - If `true`, put initial attributes on the first line.
* - `model` - The model used for interpreting the graph.
*
* @param options - Optional arguments.
* @param options.top - If given, the node to use as the top in serialization.
* @param options.indent - How to indent formatted strings.
* @param options.compact - If `true`, put initial attributes on the first line.
* @param options.model - The model used for interpreting the graph.
* @returns The PENMAN-serialized string of the graph.
* @example
* import { Graph } from 'penman-js';
*
* const g = new Graph([['h', 'instance', 'hi']]);
*
* console.log(g.toPenman());
* // '(h / hi)'
*/
toPenman(options: GraphToPenmanOptions = {}): string {
return encode(this, options);
}

/** @ignore */
__repr__() {
const name = this.constructor.name;
Expand Down
10 changes: 5 additions & 5 deletions src/lib/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,12 @@ export interface InterpretOptions {
* @example
* import { Tree, interpret } from 'penman-js';
*
* const t = new Tree('b', [
* const t = new Tree(['b', [
* ['/', 'bark-01'],
* ['ARG0', new Tree('d', [
* ['ARG0', ['d', [
* ['/', 'dog']
* ])]
* ]);
* ]]]
* ]]);
*
* const g = interpret(t);
* for (const triple of g.triples) {
Expand Down Expand Up @@ -286,7 +286,7 @@ export interface ConfigureOptions {
*
* const t = configure(g);
* console.log(t);
* // Tree('b', [['/', 'bark-01'], [':ARG0', new Tree('d', [['/', 'dog']])]])
* // Tree('b', [['/', 'bark-01'], [':ARG0', ['d', [['/', 'dog']]]]])
*/
export function configure(g: Graph, options: ConfigureOptions = {}): Tree {
const { top = g.top, model = _default_model } = options;
Expand Down
Loading