-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #45 from matthijsgroen/experiment-generic-ast
Experiment generic ast
- Loading branch information
Showing
28 changed files
with
2,103 additions
and
1,011 deletions.
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
Large diffs are not rendered by default.
Oops, something went wrong.
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,83 @@ | ||
const { traverse } = require("./traverse"); | ||
|
||
const NodeTypes = { | ||
Root: 0, | ||
Production: 1, | ||
Comment: 2, | ||
Terminal: 3, | ||
NonTerminal: 4, | ||
Choice: 5, | ||
Group: 6, | ||
Sequence: 7, | ||
Optional: 8, | ||
Repetition: 9, | ||
Special: 10, | ||
ExceptTerminal: 11, | ||
ExceptNonTerminal: 12 | ||
}; | ||
|
||
const identifyNode = node => { | ||
if (Array.isArray(node)) return NodeTypes.Root; | ||
if (node.definition) return NodeTypes.Production; | ||
if (node.choice) return NodeTypes.Choice; | ||
if (node.group) return NodeTypes.Group; | ||
if (node.comment) return NodeTypes.Comment; | ||
if (node.sequence) return NodeTypes.Sequence; | ||
if (node.optional) return NodeTypes.Optional; | ||
if (node.repetition) return NodeTypes.Repetition; | ||
// leafs | ||
if (node.specialSequence) return NodeTypes.Special; | ||
if (node.terminal) return NodeTypes.Terminal; | ||
if (node.nonTerminal) return NodeTypes.NonTerminal; | ||
if (node.exceptTerminal) return NodeTypes.ExceptTerminal; | ||
if (node.exceptNonTerminal) return NodeTypes.ExceptNonTerminal; | ||
}; | ||
|
||
const travelers = { | ||
[NodeTypes.Root]: (node, next) => node.map(next), | ||
[NodeTypes.Production]: (node, next) => ({ | ||
...node, | ||
definition: next(node.definition) | ||
}), | ||
[NodeTypes.Choice]: (node, next) => ({ | ||
...node, | ||
choice: node.choice.map(next) | ||
}), | ||
[NodeTypes.Group]: (node, next) => ({ | ||
...node, | ||
group: next(node.group) | ||
}), | ||
[NodeTypes.Sequence]: (node, next) => ({ | ||
...node, | ||
sequence: node.sequence.map(next) | ||
}), | ||
[NodeTypes.Optional]: (node, next) => ({ | ||
...node, | ||
optional: next(node.optional) | ||
}), | ||
[NodeTypes.Repetition]: (node, next) => ({ | ||
...node, | ||
repetition: next(node.repetition) | ||
}) | ||
}; | ||
|
||
const ebnfTransform = traverse(identifyNode)(travelers); | ||
|
||
const ebnfOptimizer = transformers => ast => { | ||
const optimize = ebnfTransform(transformers); | ||
let current = ast; | ||
let transformed = optimize(ast); | ||
while (current !== transformed) { | ||
current = transformed; | ||
transformed = optimize(current); | ||
} | ||
return transformed; | ||
}; | ||
|
||
module.exports = { | ||
ebnfTransform, | ||
ebnfOptimizer, | ||
NodeTypes, | ||
identifyNode, | ||
travelers | ||
}; |
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,180 @@ | ||
const { NodeTypes } = require("../ebnf-transform"); | ||
|
||
const skipFirst = list => | ||
[ | ||
list.some(e => e === "skip" || e.skip) && { skip: true }, | ||
...list.filter(e => e !== "skip" && !e.skip) | ||
].filter(Boolean); | ||
|
||
const equalElements = (first, second) => | ||
JSON.stringify(first) === JSON.stringify(second); | ||
|
||
module.exports = { | ||
[NodeTypes.Choice]: current => { | ||
if (!current.choice) return current; | ||
|
||
const isCertain = elem => | ||
(elem.terminal && elem) || (elem.nonTerminal && elem); | ||
|
||
const groupElements = elements => { | ||
const allSet = elements.every(f => f); | ||
if (!allSet) return {}; | ||
return elements.reduce((acc, elem) => { | ||
const key = JSON.stringify(elem); | ||
acc[key] = (acc[key] || 0) + 1; | ||
return acc; | ||
}, {}); | ||
}; | ||
const countSame = groupElements => { | ||
const amounts = Object.values(groupElements); | ||
return Math.max(...amounts); | ||
}; | ||
|
||
const collectCertainFirstElements = current.choice.map( | ||
elem => isCertain(elem) || (elem.sequence && isCertain(elem.sequence[0])) | ||
); | ||
const collectCertainLastElements = current.choice.map( | ||
elem => | ||
isCertain(elem) || | ||
(elem.sequence && isCertain(elem.sequence[elem.sequence.length - 1])) | ||
); | ||
const groupFirsts = groupElements(collectCertainFirstElements); | ||
const groupLasts = groupElements(collectCertainLastElements); | ||
|
||
// most wins, optimize, repeat | ||
const maxFirstsEqual = countSame(groupFirsts); | ||
const maxLastsEqual = countSame(groupLasts); | ||
if (Math.max(maxFirstsEqual, maxLastsEqual) > 1) { | ||
const beforeChoices = []; | ||
const afterChoices = []; | ||
if (maxFirstsEqual >= maxLastsEqual) { | ||
const firstElement = Object.entries(groupFirsts).find( | ||
([, value]) => value === maxFirstsEqual | ||
)[0]; | ||
|
||
// now filter all choices that have this as first element, placing | ||
// the others in 'leftOverChoices' | ||
let hasEmpty = false; | ||
let found = false; | ||
const newChoices = collectCertainFirstElements | ||
.map((elem, index) => { | ||
// if not match, add production choice to leftOverChoices. | ||
if (JSON.stringify(elem) === firstElement) { | ||
found = true; | ||
// strip off element of chain. | ||
const choice = current.choice[index]; | ||
if (choice.sequence) { | ||
return { ...choice, sequence: choice.sequence.slice(1) }; | ||
} else { | ||
hasEmpty = true; | ||
} | ||
} else { | ||
(found ? afterChoices : beforeChoices).push( | ||
current.choice[index] | ||
); | ||
} | ||
}) | ||
.filter(Boolean); | ||
const newElements = [ | ||
JSON.parse(firstElement), | ||
newChoices.length > 0 && | ||
hasEmpty && { | ||
optional: | ||
newChoices.length == 1 ? newChoices[0] : { choice: newChoices } | ||
}, | ||
newChoices.length > 0 && | ||
!hasEmpty && | ||
(newChoices.length == 1 ? newChoices[0] : { choice: newChoices }) | ||
].filter(Boolean); | ||
const replacementElement = | ||
newElements.length > 1 ? { sequence: newElements } : newElements[0]; | ||
|
||
const finalResult = | ||
beforeChoices.length + afterChoices.length > 0 | ||
? { | ||
choice: [] | ||
.concat(beforeChoices) | ||
.concat(replacementElement) | ||
.concat(afterChoices) | ||
} | ||
: replacementElement; | ||
|
||
return finalResult; | ||
} else { | ||
const lastElement = Object.entries(groupLasts).find( | ||
([, value]) => value === maxLastsEqual | ||
)[0]; | ||
|
||
// now filter all choices that have this as first element, placing | ||
// the others in 'leftOverChoices' | ||
let hasEmpty = false; | ||
let found = false; | ||
const newChoices = collectCertainLastElements | ||
.map((elem, index) => { | ||
// if not match, add production choice to leftOverChoices. | ||
if (JSON.stringify(elem) === lastElement) { | ||
found = true; | ||
// strip off element of chain. | ||
const choice = current.choice[index]; | ||
if (choice.sequence) { | ||
return { ...choice, sequence: choice.sequence.slice(0, -1) }; | ||
} else { | ||
hasEmpty = true; | ||
} | ||
} else { | ||
(found ? afterChoices : beforeChoices).push( | ||
current.choice[index] | ||
); | ||
} | ||
}) | ||
.filter(Boolean); | ||
const newElements = [ | ||
newChoices.length > 0 && | ||
hasEmpty && { | ||
optional: | ||
newChoices.length == 1 ? newChoices[0] : { choice: newChoices } | ||
}, | ||
newChoices.length > 0 && | ||
!hasEmpty && | ||
(newChoices.length == 1 ? newChoices[0] : { choice: newChoices }), | ||
JSON.parse(lastElement) | ||
].filter(Boolean); | ||
const replacementElement = | ||
newElements.length > 1 ? { sequence: newElements } : newElements[0]; | ||
|
||
const finalResult = | ||
beforeChoices.length + afterChoices.length > 0 | ||
? { | ||
choice: [] | ||
.concat(beforeChoices) | ||
.concat(replacementElement) | ||
.concat(afterChoices) | ||
} | ||
: replacementElement; | ||
|
||
return finalResult; | ||
} | ||
} | ||
|
||
// Merge remaining choices | ||
const result = { | ||
...current, | ||
choice: skipFirst( | ||
current.choice | ||
.map(item => { | ||
const optimizedItem = item; | ||
if (optimizedItem.choice) { | ||
return optimizedItem.choice; | ||
} else { | ||
return [optimizedItem]; | ||
} | ||
}) | ||
.reduce((acc, item) => acc.concat(item), []) | ||
) | ||
}; | ||
if (equalElements(result, current)) { | ||
return current; | ||
} | ||
return result; | ||
} | ||
}; |
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,36 @@ | ||
const { NodeTypes } = require("../ebnf-transform"); | ||
|
||
module.exports = { | ||
[NodeTypes.Optional]: current => { | ||
if (!current.optional || !current.optional.choice) { | ||
return current; | ||
} | ||
return { | ||
choice: [{ skip: true }].concat( | ||
current.optional.choice | ||
.filter(node => !node.skip) | ||
.map(node => (node.repetition ? { ...node, skippable: false } : node)) | ||
) | ||
}; | ||
}, | ||
[NodeTypes.Choice]: current => { | ||
if (!current.choice) { | ||
return current; | ||
} | ||
const hasSkippableRepetition = current.choice.some( | ||
node => node.repetition && node.skippable | ||
); | ||
if (hasSkippableRepetition) { | ||
return { | ||
choice: [{ skip: true }].concat( | ||
current.choice | ||
.filter(node => !node.skip) | ||
.map( | ||
node => (node.repetition ? { ...node, skippable: false } : node) | ||
) | ||
) | ||
}; | ||
} | ||
return current; | ||
} | ||
}; |
Oops, something went wrong.