-
Notifications
You must be signed in to change notification settings - Fork 77
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
BaseNode.equals support, and other ast goodness from python-fluent #172
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
fb23d5c
BaseNode.equals
Pike b7109e7
BaseNode.clone()
Pike 387bc7e
Add Visitor and Transformer APIs from python's fluent.syntax
Pike dbe7134
Review comments. Mostly camelCases. And Object.create
Pike 0eaa60b
Remove .sortingKey, it's gone in python, too
Pike File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { BaseNode } from "./ast"; | ||
|
||
/* | ||
* Abstract Visitor pattern | ||
*/ | ||
export class Visitor { | ||
visit(node) { | ||
if (Array.isArray(node)) { | ||
node.forEach(child => this.visit(child)); | ||
return; | ||
} | ||
if (!(node instanceof BaseNode)) { | ||
return; | ||
} | ||
const visit = this[`visit${node.type}`] || this.genericVisit; | ||
visit.call(this, node); | ||
} | ||
|
||
genericVisit(node) { | ||
for (const propname of Object.keys(node)) { | ||
this.visit(node[propname]); | ||
} | ||
} | ||
} | ||
|
||
/* | ||
* Abstract Transformer pattern | ||
*/ | ||
export class Transformer extends Visitor { | ||
visit(node) { | ||
if (!(node instanceof BaseNode)) { | ||
return node; | ||
} | ||
const visit = this[`visit${node.type}`] || this.genericVisit; | ||
return visit.call(this, node); | ||
} | ||
|
||
genericVisit(node) { | ||
for (const propname of Object.keys(node)) { | ||
const propvalue = node[propname]; | ||
if (Array.isArray(propvalue)) { | ||
const newvals = propvalue | ||
.map(child => this.visit(child)) | ||
.filter(newchild => newchild !== undefined); | ||
node[propname] = newvals; | ||
} | ||
if (propvalue instanceof BaseNode) { | ||
const new_val = this.visit(propvalue); | ||
if (new_val === undefined) { | ||
delete node[propname]; | ||
} else { | ||
node[propname] = new_val; | ||
} | ||
} | ||
} | ||
return node; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
"use strict"; | ||
|
||
import assert from "assert"; | ||
import { ftl } from "./util"; | ||
import { FluentParser } from "../src"; | ||
import * as AST from "../src/ast"; | ||
|
||
suite("BaseNode.equals", function() { | ||
setup(function() { | ||
this.parser = new FluentParser(); | ||
}); | ||
test("Identifier.equals", function() { | ||
const thisNode = new AST.Identifier("name"); | ||
const otherNode = new AST.Identifier("name"); | ||
assert.ok(thisNode.clone() instanceof AST.Identifier); | ||
assert.strictEqual(thisNode.equals(otherNode), true); | ||
assert.strictEqual(thisNode.equals(thisNode.clone()), true); | ||
assert.notStrictEqual(thisNode, thisNode.clone()); | ||
}); | ||
test("Node.type", function() { | ||
const thisNode = new AST.Identifier("name"); | ||
const otherNode = new AST.StringLiteral("name"); | ||
assert.strictEqual(thisNode.equals(otherNode), false); | ||
}); | ||
test("Array children", function() { | ||
const thisNode = new AST.Pattern([ | ||
new AST.TextElement("one"), | ||
new AST.TextElement("two"), | ||
new AST.TextElement("three"), | ||
]); | ||
let otherNode = new AST.Pattern([ | ||
new AST.TextElement("one"), | ||
new AST.TextElement("two"), | ||
new AST.TextElement("three"), | ||
]); | ||
assert.strictEqual(thisNode.equals(otherNode), true); | ||
}); | ||
test("Variant order matters", function() { | ||
const thisRes = this.parser.parse(ftl` | ||
msg = { $val -> | ||
[few] things | ||
[1] one | ||
*[other] default | ||
} | ||
`); | ||
const otherRes = this.parser.parse(ftl` | ||
msg = { $val -> | ||
[few] things | ||
*[other] default | ||
[1] one | ||
} | ||
`); | ||
const thisNode = thisRes.body[0]; | ||
const otherNode = otherRes.body[0]; | ||
assert.strictEqual(thisNode.equals(otherNode), false); | ||
assert.strictEqual(thisRes.equals(thisRes.clone(), []), true); | ||
assert.notStrictEqual(thisRes, thisRes.clone()); | ||
}); | ||
test("Attribute order matters", function() { | ||
const thisRes = this.parser.parse(ftl` | ||
msg = | ||
.attr1 = one | ||
.attr2 = two | ||
`); | ||
const otherRes = this.parser.parse(ftl` | ||
msg = | ||
.attr2 = two | ||
.attr1 = one | ||
`); | ||
const thisNode = thisRes.body[0]; | ||
const otherNode = otherRes.body[0]; | ||
assert.strictEqual(thisNode.equals(otherNode), false); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
"use strict"; | ||
|
||
import assert from "assert"; | ||
import { ftl } from "./util"; | ||
import { FluentParser, Visitor, Transformer } from "../src"; | ||
|
||
suite("Visitor", function() { | ||
setup(function() { | ||
const parser = new FluentParser(); | ||
this.resource = parser.parse(ftl` | ||
one = Message | ||
# Comment | ||
two = Messages | ||
three = Messages with | ||
.an = Attribute | ||
`); | ||
}); | ||
test("Mock Visitor", function() { | ||
class MockVisitor extends Visitor { | ||
constructor() { | ||
super(); | ||
this.calls = {}; | ||
this.pattern_calls = 0; | ||
} | ||
genericVisit(node) { | ||
const nodename = node.type; | ||
if (nodename in this.calls) { | ||
this.calls[nodename]++; | ||
} else { | ||
this.calls[nodename] = 1; | ||
} | ||
super.genericVisit(node); | ||
} | ||
visitPattern(node) { | ||
this.pattern_calls++; | ||
} | ||
} | ||
const mv = new MockVisitor(); | ||
mv.visit(this.resource); | ||
assert.strictEqual(mv.pattern_calls, 4); | ||
assert.deepStrictEqual( | ||
mv.calls, | ||
{ | ||
'Resource': 1, | ||
'Comment': 1, | ||
'Message': 3, | ||
'Identifier': 4, | ||
'Attribute': 1, | ||
'Span': 10, | ||
} | ||
) | ||
}); | ||
test("WordCount", function() { | ||
class VisitorCounter extends Visitor { | ||
constructor() { | ||
super(); | ||
this.word_count = 0; | ||
} | ||
genericVisit(node) { | ||
switch (node.type) { | ||
case 'Span': | ||
case 'Annotation': | ||
break; | ||
default: | ||
super.genericVisit(node); | ||
} | ||
} | ||
visitTextElement(node) { | ||
this.word_count += node.value.split(/\s+/).length; | ||
} | ||
} | ||
const vc = new VisitorCounter(); | ||
vc.visit(this.resource); | ||
assert.strictEqual(vc.word_count, 5); | ||
}) | ||
}); | ||
|
||
suite("Transformer", function() { | ||
setup(function() { | ||
const parser = new FluentParser(); | ||
this.resource = parser.parse(ftl` | ||
one = Message | ||
# Comment | ||
two = Messages | ||
three = Messages with | ||
.an = Attribute | ||
`); | ||
}); | ||
test("ReplaceTransformer", function() { | ||
class ReplaceTransformer extends Transformer { | ||
constructor(before, after) { | ||
super(); | ||
this.before = before; | ||
this.after = after; | ||
} | ||
genericVisit(node) { | ||
switch (node.type) { | ||
case 'Span': | ||
case 'Annotation': | ||
return node; | ||
break; | ||
default: | ||
return super.genericVisit(node); | ||
} | ||
} | ||
visitTextElement(node) { | ||
node.value = node.value.replace(this.before, this.after); | ||
return node; | ||
} | ||
} | ||
const resource = this.resource.clone() | ||
const transformed = new ReplaceTransformer('Message', 'Term').visit(resource); | ||
assert.notStrictEqual(resource, this.resource); | ||
assert.strictEqual(resource, transformed); | ||
assert.strictEqual(this.resource.equals(transformed), false); | ||
assert.strictEqual(transformed.body[1].value.elements[0].value, 'Terms'); | ||
}); | ||
}); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's add a test which verifies that the original nodes have their variants in unchanged order after these tests run, please.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm dropping the ordering completely, per comment above, and update the tests accordingly. Let me know if there's more to do here.