-
Notifications
You must be signed in to change notification settings - Fork 4
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
Changed alias expansion to allow for nested aliases #125
base: master
Are you sure you want to change the base?
Changes from all commits
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 |
---|---|---|
@@ -1,3 +1,133 @@ | ||
function cartesianProduct<T>(arr: T[][]): T[][] { | ||
return arr.reduce( | ||
(a, b) => { | ||
return a | ||
.map(x => { | ||
return b.map(y => { | ||
return x.concat(y); | ||
}); | ||
}) | ||
.reduce((c, d) => c.concat(d), []); | ||
}, | ||
[[]] as T[][] | ||
); | ||
} | ||
|
||
export function* aliasesFromPattern(query: string) { | ||
if (!query || query.length === 0) { | ||
throw new Error('Tried to parse null or empty string'); | ||
} | ||
|
||
let i = 0; | ||
const currentChar = () => query[i]; | ||
|
||
function match(c: string) { | ||
const char = currentChar(); | ||
if (c.includes(char)) { | ||
i++; | ||
return char; | ||
} | ||
else if (i >= query.length) { | ||
throw new Error( | ||
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. Consider throwing your own class here that inherits from Error. Then when you catch, use instanceof operator to detect your error. Re-throw other errors. |
||
`Ran off the end of the string expecting to find: '${c}', i: ${i}` | ||
); | ||
} else { | ||
throw new Error( | ||
`Unexpected character: '${currentChar()}'. Expected '${c}'.` | ||
); | ||
} | ||
} | ||
|
||
//Intrinsics | ||
const whitespace = () => match(' '); | ||
const letter = () => | ||
match( | ||
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. Consider using on of the built-in iswhitespace methods here to handle other locals. 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. If not, then define the constant outside of the function. (To avoid performing string concat every call) |
||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + | ||
'abcdefghijklmnopqrstuvwxyz' + | ||
'ÁáÍíøOóÚúÜüÑñ' + | ||
' ' | ||
); | ||
const token = () => [kleenePlus(letter).join('')]; | ||
const constant = (s: string) => { | ||
for (let i = 0; i < s.length; i++) match(s.charAt(i)); | ||
}; | ||
const separator = () => match('|,;'); | ||
|
||
//Lists | ||
const listGap = () => { | ||
separator(); | ||
kleene(whitespace); | ||
}; | ||
const group = () => kleenePlus(phrase, listGap); | ||
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. May be good, for the sake of readability to define |
||
|
||
//Branches | ||
function choice(): string[] { | ||
constant('('); | ||
const contents = group(); | ||
constant(')'); | ||
return ([] as string[]).concat(...contents); | ||
} | ||
|
||
function optional(): string[] { | ||
constant('['); | ||
const contents = group(); | ||
constant(']'); | ||
const result = ([] as string[]).concat(...contents); | ||
result.push(''); | ||
return result; | ||
} | ||
|
||
const term = () => union(choice, optional, token); | ||
const phrase = () => cartesianProduct(kleenePlus(term)).map(a => a.join('')); | ||
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. Haha, took a second to decompose this one.
Wouldn't be a bad idea to document this! A little explanation by the dev saves a lot of reverse engineering by the reader. |
||
const grammar = () => phrase(); | ||
|
||
const results = grammar(); | ||
if (i !== query.length) { | ||
throw new TypeError('Failed expanding alias at index ' + i); | ||
} | ||
|
||
yield* aliasesFromPatternHelper(results); | ||
|
||
function kleene<T>(match: () => T, separator?: () => void): T[] { | ||
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. Overloading the term Please avoid instances of variable overshadowing here, and in other locations (such as |
||
const results: T[] = []; | ||
try { | ||
while (true) { | ||
results.concat(match()); | ||
if (separator) separator(); | ||
} | ||
} catch (e) { | ||
return results; | ||
} | ||
} | ||
|
||
function kleenePlus<T>(match: () => T, separator?: () => void): T[] { | ||
const results: T[] = []; | ||
results.push(match()); | ||
try { | ||
while (true) { | ||
if (separator) separator(); | ||
results.push(match()); | ||
} | ||
} catch (e) { | ||
return results; | ||
} | ||
} | ||
|
||
function union<T>(...matches: Array<() => T>) { | ||
const backtrackPointer = i; | ||
const errors: string[] = []; | ||
for (const match of matches) { | ||
try { | ||
return match(); | ||
} catch (e) { | ||
errors.push(e); | ||
i = backtrackPointer; | ||
} | ||
} | ||
throw new Error('Matched none of union:\n- ' + errors.join('\n- ')); | ||
} | ||
} | ||
|
||
function combine(left: string, right: string) { | ||
if (left.length === 0) { | ||
return right; | ||
|
@@ -9,52 +139,12 @@ function combine(left: string, right: string) { | |
} | ||
|
||
function* aliasesFromPatternHelper( | ||
prefix: string, | ||
options: string[][] | ||
options: string[] | ||
): IterableIterator<string> { | ||
if (options.length > 0) { | ||
for (const option of options[0]) { | ||
yield* aliasesFromPatternHelper( | ||
combine(prefix, option), | ||
options.slice(1) | ||
); | ||
for (const option of options) { | ||
yield option.replace(/\s\s+/g, ' ').trim(); | ||
} | ||
} else { | ||
yield prefix; | ||
} | ||
} | ||
|
||
export function* aliasesFromPattern(query: string) { | ||
const m = /(\[[^\]]*\])|(\([^\)]*\))|([^\[^\()]*)/g; | ||
|
||
// Remove leading, trailing, and consecutive spaces. | ||
const query2 = query.replace(/\s+/g, ' ').trim(); | ||
|
||
// Throw on comma before ] and ). | ||
if (query2.search(/(,\])|(,\))/g) !== -1) { | ||
throw TypeError(`generateAliases: illegal trailing comma in "${query}".`); | ||
} | ||
|
||
const matches = query2.match(m); | ||
|
||
if (matches !== null) { | ||
const options = matches | ||
.map(match => { | ||
if (match.startsWith('[')) { | ||
// Selects an option or leaves blank | ||
return [...match.slice(1, -1).split(','), ''].map(x => x.trim()); | ||
} else if (match.startsWith('(')) { | ||
// Must select from one of the options | ||
return match | ||
.slice(1, -1) | ||
.split(',') | ||
.map(x => x.trim()); | ||
} else { | ||
return [match.trim()]; | ||
} | ||
}) | ||
.filter(match => match[0].length > 0); | ||
yield* aliasesFromPatternHelper('', options); | ||
} | ||
} | ||
|
||
|
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.
}
should be on the same line aselse if
Run
gts check
at project root to run linter to see style issues in the project as a whole.