Skip to content

Reference Resolver Prototype #263

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

Closed
wants to merge 36 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
8ffeb4b
Rename all files to *.js; invoke with node -r esm
stasm Jun 5, 2019
cb05d14
Use Prettier
stasm Jun 5, 2019
cc9e5a1
Add VS Code workspace settings
stasm Jun 5, 2019
bf7ff79
Resolve string variable reference
stasm May 28, 2019
1d2b7cd
Build TS into dist/
stasm May 29, 2019
518ae2d
IResult
stasm May 29, 2019
dbb027c
Use Prettier
stasm May 29, 2019
94f15ee
Rename Resolver to Scope
stasm May 30, 2019
23d1a99
Add the IValue interface
stasm May 30, 2019
481a09d
Add IMessageReference
stasm May 30, 2019
d8958b1
Add the runtime Message class
stasm May 30, 2019
376dd3c
Move fixtures into a new file
stasm May 30, 2019
596cddd
Add ISelectExpression
stasm May 30, 2019
3fadeb1
Remove the I prefix from interfaces
stasm May 30, 2019
956786b
Add PatternElement type alias
stasm May 30, 2019
655701c
Add Expression, InlineExpression
stasm May 30, 2019
59d4f7b
Don't use parameter properties in constructors
stasm May 31, 2019
67baf2b
Add Bundle
stasm May 31, 2019
45b4d6b
Use a tagged union for Result<T>
stasm Jun 4, 2019
05f3de5
Remove Result.map
stasm Jun 4, 2019
8a5d351
Use Rusty names of Result's methods
stasm Jun 4, 2019
d0a7fe2
Use inline callback types
stasm Jun 4, 2019
2d9a3ea
Add ScopeError stub
stasm Jun 4, 2019
138e0c7
Add a few sample tests
stasm Jun 4, 2019
1818e8c
Add bin/format
stasm Jun 5, 2019
5c93fe2
Print results as JSON
stasm Jun 5, 2019
66bd9b3
Sample test fixtures
stasm Jun 5, 2019
4bfceff
New folder structure, part 1
stasm Jun 5, 2019
21ef007
New folder structure, part 2
stasm Jun 5, 2019
bb97aa4
Add test:resolver to npm test
stasm Jun 5, 2019
b5acf37
test/syntax, test/resolver
stasm Jun 6, 2019
ce26f1d
Move special EOL attirbutes into test/syntax/fixtures
stasm Jun 6, 2019
593c4c5
Factor out test code for comparing fixtures
stasm Jun 6, 2019
267ad11
Override file-specific settings in VS Code
stasm Jun 6, 2019
34c14c3
Factor out common parsing and formatting operations to lib/tools
stasm Jun 6, 2019
08f1568
Add npm run test:resolver:fixtures
stasm Jun 6, 2019
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
24 changes: 0 additions & 24 deletions .eslintrc

This file was deleted.

4 changes: 1 addition & 3 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
*.ftl eol=lf
test/fixtures/crlf.ftl eol=crlf
test/fixtures/cr.ftl eol=cr
**.ftl eol=lf
6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"bracketSpacing": false,
"trailingComma": "es5",
"printWidth": 100,
"tabWidth": 4
}
9 changes: 9 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"editor.formatOnSave": true,
"[javascript]": {
"editor.formatOnSave": true
},
"[typescript]": {
"editor.formatOnSave": true
}
}
13 changes: 13 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"problemMatcher": [
"$tsc-watch"
],
"isBackground": true
}
]
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ validate your work:
npm test # Test the parser against JSON AST fixtures.
npm run lint # Lint the parser code.

npm run generate:ebnf # Generate the EBNF from syntax/grammar.mjs.
npm run generate:ebnf # Generate the EBNF from syntax/grammar.js.
npm run generate:fixtures # Generate test fixtures (FTL → JSON AST).

npm run build:guide # Build the HTML version of the Guide.
Expand Down
11 changes: 5 additions & 6 deletions bin/ebnf.mjs → bin/ebnf.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from "fs";
import readline from "readline";
import parse_args from "minimist";
import ebnf from "../lib/ebnf.mjs";
import ebnf from "../lib/ebnf.js";

const argv = parse_args(process.argv.slice(2), {
boolean: ["help"],
Expand All @@ -26,14 +26,14 @@ if (file_path === "-") {

function exit_help(exit_code) {
console.log(`
Usage: node --experimental-modules ebnf.mjs [OPTIONS] <FILE>
Usage: node -r esm ebnf.js [OPTIONS] <FILE>

When FILE is "-", read text from stdin.

Examples:

node --experimental-modules ebnf.mjs path/to/grammar.mjs
cat path/to/grammar.mjs | node --experimental-modules ebnf.mjs -
node -r esm ebnf.js path/to/grammar.js
cat path/to/grammar.js | node -r esm ebnf.js -

Options:

Expand All @@ -52,8 +52,7 @@ function from_stdin() {
const lines = [];

rl.on("line", line => lines.push(line));
rl.on("close", () =>
print_ebnf(lines.join("\n") + "\n"));
rl.on("close", () => print_ebnf(lines.join("\n") + "\n"));
}

function from_file(file_path) {
Expand Down
55 changes: 55 additions & 0 deletions bin/format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import parseArgs from "minimist";
import {fromStdin, fromFile} from "../lib/input";
import {formatGroups, formatResource, parseString} from "../lib/tools";

const argv = parseArgs(process.argv.slice(2), {
boolean: ["help", "group"],
alias: {
help: "h",
group: "g",
},
});

if (argv.help) {
exitHelp(0);
}

const [filePath] = argv._;

if (filePath === "-") {
fromStdin(print);
} else if (filePath) {
fromFile(filePath, print);
} else {
exitHelp(1);
}

function exitHelp(exitCode: number) {
console.log(`
Usage: node format.js [OPTIONS] <FILE>

When FILE is "-", read text from stdin.

Examples:

node format.js path/to/file.ftl
cat path/to/file.ftl | node format.js -

Options:

-h, --help Display help and quit.
-g, --group Treat each group as a separate resource.
`);
process.exit(exitCode);
}

function print(source: string) {
let resource = parseString(source);
if (argv.group) {
let results = formatGroups(resource);
console.log(JSON.stringify(results, null, 4));
} else {
let results = formatResource(resource, new Map());
console.log(JSON.stringify(results, null, 4));
}
}
15 changes: 7 additions & 8 deletions bin/parse.mjs → bin/parse.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from "fs";
import readline from "readline";
import parse_args from "minimist";
import {Resource} from "../syntax/grammar.mjs";
import {Resource} from "../syntax/grammar.js";

const argv = parse_args(process.argv.slice(2), {
boolean: ["help"],
Expand All @@ -26,14 +26,14 @@ if (file_path === "-") {

function exit_help(exit_code) {
console.log(`
Usage: node --experimental-modules parse.mjs [OPTIONS] <FILE>
Usage: node -r esm parse.js [OPTIONS] <FILE>

When FILE is "-", read text from stdin.

Examples:

node --experimental-modules parse.mjs path/to/file.ftl
cat path/to/file.ftl | node --experimental-modules parse.mjs -
node -r esm parse.js path/to/file.ftl
cat path/to/file.ftl | node -r esm parse.js -

Options:

Expand All @@ -52,8 +52,7 @@ function parse_stdin() {
const lines = [];

rl.on("line", line => lines.push(line));
rl.on("close", () =>
parse(lines.join("\n") + "\n"));
rl.on("close", () => parse(lines.join("\n") + "\n"));
}

function parse_file(file_path) {
Expand All @@ -66,9 +65,9 @@ function parse_file(file_path) {
});
}


function parse(ftl) {
Resource.run(ftl).fold(
ast => console.log(JSON.stringify(ast, null, 4)),
err => console.error(err));
err => console.error(err)
);
}
14 changes: 7 additions & 7 deletions lib/README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
# Fluent Specification Support Code

This directory contains support code for the parser-combinator underlying the syntax code, as well as the code to transform `grammar.mjs` to `fluent.ebnf`. The combination allows for a succinct formulation of the syntax in `grammar.mjs` and a readable representation of that in `fluent.ebnf`.
This directory contains support code for the parser-combinator underlying the syntax code, as well as the code to transform `grammar.js` to `fluent.ebnf`. The combination allows for a succinct formulation of the syntax in `grammar.js` and a readable representation of that in `fluent.ebnf`.

## Parser-Combinator

**`parser.mjs`** is the base parser class, **`stream.mjs`** holds a iterator over the strings to be parsed.
**`parser.js`** is the base parser class, **`stream.js`** holds a iterator over the strings to be parsed.

**`combinators.mjs`** holds the actual basic grammar productions and logical combinations of grammar productions.
**`combinators.js`** holds the actual basic grammar productions and logical combinations of grammar productions.

Both use `Success` and `Failure` from **`result.mjs`** to pass success and failure conditions forward.
Both use `Success` and `Failure` from **`result.js`** to pass success and failure conditions forward.

After the definition of grammar productions, the utilities in **`mappers.mjs`** are used to concat, flatten, and extract the matched data to prepare it for AST generation.
After the definition of grammar productions, the utilities in **`mappers.js`** are used to concat, flatten, and extract the matched data to prepare it for AST generation.

## EBNF Generation

The `fluent.ebnf` is created by parsing the `grammar.mjs` and transpilation to an EBNF file. The `babylon` JavaScript parser is used to load a Babel AST.
The `fluent.ebnf` is created by parsing the `grammar.js` and transpilation to an EBNF file. The `babylon` JavaScript parser is used to load a Babel AST.

**`ebnf.mjs`** is the top-level entry point used by `bin/ebnf.mjs`. It uses the iteration logic from **`walker.mjs`** to go over the Babel AST and to extract the information relevant to the EBNF via the Visitor in **`visitor.mjs`**. The resulting data is then serialiazed to EBNF via **`serializer.mjs`**.
**`ebnf.js`** is the top-level entry point used by `bin/ebnf.js`. It uses the iteration logic from **`walker.js`** to go over the Babel AST and to extract the information relevant to the EBNF via the Visitor in **`visitor.js`**. The resulting data is then serialiazed to EBNF via **`serializer.js`**.
73 changes: 36 additions & 37 deletions lib/combinators.mjs → lib/combinators.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import Parser from "./parser.mjs";
import {Success, Failure} from "./result.mjs";
import {join} from "./mappers.mjs";
import Parser from "./parser.js";
import {Success, Failure} from "./result.js";
import {join} from "./mappers.js";

export function defer(fn) {
// Parsers may be defined as defer(() => parser) to avoid cyclic
// dependecies.
return new Parser(stream =>
fn().run(stream));
return new Parser(stream => fn().run(stream));
}

export function string(str) {
return new Parser(stream =>
stream.head(str.length) === str
? new Success(str, stream.move(str.length))
: new Failure(`${str} not found`, stream));
: new Failure(`${str} not found`, stream)
);
}

export function regex(re) {
Expand All @@ -38,16 +38,16 @@ export function eof() {
return new Parser(stream =>
stream.head() === Symbol.for("eof")
? new Success(stream.head(), stream.move(1))
: new Failure("not at EOF", stream));
: new Failure("not at EOF", stream)
);
}

export function lookahead(parser) {
return new Parser(stream =>
parser
.run(stream)
.fold(
value => new Success(value, stream),
value => new Failure(value, stream)));
.fold(value => new Success(value, stream), value => new Failure(value, stream))
);
}

export function not(parser) {
Expand All @@ -56,13 +56,14 @@ export function not(parser) {
.run(stream)
.fold(
(value, tail) => new Failure("not failed", stream),
(value, tail) => new Success(null, stream)));
(value, tail) => new Success(null, stream)
)
);
}

export function and(...parsers) {
const final = parsers.pop();
return sequence(...parsers.map(lookahead), final)
.map(results => results[results.length - 1]);
return sequence(...parsers.map(lookahead), final).map(results => results[results.length - 1]);
}

export function either(...parsers) {
Expand Down Expand Up @@ -91,45 +92,43 @@ export function maybe(parser) {
.run(stream)
.fold(
(value, tail) => new Success(value, tail),
(value, tail) => new Success(null, stream)));
(value, tail) => new Success(null, stream)
)
);
}

export function append(p1, p2) {
return p1.chain(values =>
p2.map(value => values.concat([value])));
return p1.chain(values => p2.map(value => values.concat([value])));
}

export function after(prefix, parser) {
return sequence(prefix, parser)
.map(([left, value]) => value);
return sequence(prefix, parser).map(([left, value]) => value);
}

export function sequence(...parsers) {
return parsers.reduce(
(acc, parser) => append(acc, parser),
always([]));
return parsers.reduce((acc, parser) => append(acc, parser), always([]));
}

export function repeat(parser) {
return new Parser(stream =>
parser
.run(stream)
.fold(
(value, tail) =>
repeat(parser)
.map(rest => [value].concat(rest))
.run(tail),
(value, tail) => new Success([], stream)));
parser.run(stream).fold(
(value, tail) =>
repeat(parser)
.map(rest => [value].concat(rest))
.run(tail),
(value, tail) => new Success([], stream)
)
);
}

export function repeat1(parser) {
return new Parser(stream =>
parser
.run(stream)
.fold(
(value, tail) =>
repeat(parser)
.map(rest => [value].concat(rest))
.run(tail),
(value, tail) => new Failure("repeat1 failed", stream)));
parser.run(stream).fold(
(value, tail) =>
repeat(parser)
.map(rest => [value].concat(rest))
.run(tail),
(value, tail) => new Failure("repeat1 failed", stream)
)
);
}
13 changes: 13 additions & 0 deletions lib/ebnf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {parse} from "babylon";
import walk from "./walker.js";
import visitor from "./visitor.js";
import serialize from "./serializer.js";

export default function ebnf(source, min_name_length = 0) {
let grammar_ast = parse(source, {sourceType: "module"});
let rules = walk(grammar_ast, visitor);
let state = {
max_name_length: Math.max(min_name_length, ...rules.map(rule => rule.name.length)),
};
return rules.map(rule => serialize(rule, state)).join("");
}
Loading