-
Notifications
You must be signed in to change notification settings - Fork 80
BaseNode.equals support, and other ast goodness from python-fluent #172
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
Changes from 3 commits
fb23d5c
b7109e7
387bc7e
dbe7134
0eaa60b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
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 nodename = node.type; | ||
const visit = this[`visit_${nodename}`] || this.generic_visit; | ||
stasm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
visit.call(this, node); | ||
} | ||
|
||
generic_visit(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 nodename = node.type; | ||
const visit = this[`visit_${nodename}`] || this.generic_visit; | ||
return visit.call(this, node); | ||
} | ||
|
||
generic_visit(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; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
"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.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("Variants", function() { | ||
const thisRes = this.parser.parse(ftl` | ||
msg = { $val -> | ||
[few] things | ||
[1] one | ||
*[other] default | ||
} | ||
`); | ||
const otherRes = this.parser.parse(ftl` | ||
# a comment | ||
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(thisNode.equals(otherNode, ['span', 'comment']), true); | ||
assert.strictEqual(thisNode.value.equals(otherNode.value), true); | ||
assert.strictEqual(thisNode.value.equals(otherNode.value, []), false); | ||
assert.strictEqual(thisRes.equals(thisRes.clone(), []), true); | ||
assert.notStrictEqual(thisRes, thisRes.clone()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe 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. |
||
}); | ||
test("Attributes without order", 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), true); | ||
}); | ||
}); |
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; | ||
} | ||
generic_visit(node) { | ||
const nodename = node.type; | ||
if (nodename in this.calls) { | ||
this.calls[nodename]++; | ||
} else { | ||
this.calls[nodename] = 1; | ||
} | ||
super.generic_visit(node); | ||
} | ||
visit_Pattern(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; | ||
} | ||
generic_visit(node) { | ||
switch (node.type) { | ||
case 'Span': | ||
case 'Annotation': | ||
break; | ||
default: | ||
super.generic_visit(node); | ||
} | ||
} | ||
visit_TextElement(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; | ||
} | ||
generic_visit(node) { | ||
switch (node.type) { | ||
case 'Span': | ||
case 'Annotation': | ||
return node; | ||
break; | ||
default: | ||
return super.generic_visit(node); | ||
} | ||
} | ||
visit_TextElement(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'); | ||
}); | ||
}); |
Uh oh!
There was an error while loading. Please reload this page.