Last active
January 29, 2025 14:06
-
-
Save Hugoberry/7fcfddfdb64357791cf2524677924c12 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
@{% | |
const moo = require('moo'); | |
const lexer = moo.compile({ | |
ws: /[ \t]+/, | |
newline: { match: /\n/, lineBreaks: true }, | |
colon: ':', | |
equals: '=', | |
lparen: '(', | |
rparen: ')', | |
angle_expr: { | |
match: /<(?:[^<>]+|<[^<>]*>)*>/, | |
value: x => x.slice(1, -1) | |
}, | |
comma: ',', | |
operator_type: ['RelLogOp', 'ScaLogOp', 'LookupPhyOp', 'IterPhyOp', 'SpoolPhyOp'], | |
hash: '#', | |
keyword: ['Boolean', 'Integer', 'String', 'Currency', 'Double', 'NONE', 'BLANK'], | |
empty_parens: { | |
match: /\(\)/, | |
value: x => '()' | |
}, | |
number_range: { | |
match: /[0-9]+-[0-9]+/, | |
value: x => x | |
}, | |
// Handle empty column refs first | |
empty_column_ref: { | |
match: /''\[[^\]]*\]/, | |
value: x => x | |
}, | |
// Then regular column refs | |
column_ref: { | |
match: /'[^']*'\[[^\]]*\]/, | |
value: x => x | |
}, | |
measure_ref: { | |
match: /\[[^\]]*\]/, | |
value: x => x | |
}, | |
number: { | |
match: /[0-9]+(?:\.[0-9]+)?/, | |
value: x => Number(x) | |
}, | |
identifier: { | |
match: /[a-zA-Z_][a-zA-Z0-9_.]*/, | |
type: 'identifier' | |
}, | |
}); | |
%} | |
@lexer lexer | |
main -> lines {% | |
function(d) { | |
return buildTree(d[0]); | |
} | |
%} | |
lines -> line (%newline line):* {% | |
function(d) { | |
const [first, rest] = d; | |
const result = [first]; | |
if (rest) { | |
rest.forEach(([, line]) => result.push(line)); | |
} | |
return result; | |
} | |
%} | |
line -> _ statement {% | |
function(d) { | |
const [indent, stmt] = d; | |
return { | |
indent: indent ? indent.length : 0, | |
...stmt | |
}; | |
} | |
%} | |
statement -> operator angle_params:? %colon rest_of_line {% | |
function(d) { | |
const [op, params,, properties] = d; | |
return { | |
operator: op, | |
parameters: params, | |
properties: properties || {} | |
}; | |
} | |
%} | |
operator -> | |
%identifier {% d => d[0].value %} | |
| %column_ref {% d => d[0].value %} | |
| %empty_column_ref {% d => d[0].value %} | |
angle_params -> %angle_expr {% d => d[0].value %} | |
rest_of_line -> (_ token):* {% | |
function(d) { | |
const tokens = d[0].map(x => x[1]); | |
const properties = {}; | |
let current = null; | |
let inParens = false; | |
let parenContent = []; | |
for (let i = 0; i < tokens.length; i++) { | |
const token = tokens[i]; | |
if (token.type === 'operator_type') { | |
properties.type = token.value; | |
continue; | |
} | |
if (token.type === 'keyword') { | |
if (current) { | |
parenContent.push(token.value); | |
} else { | |
properties.value = token.value; | |
} | |
continue; | |
} | |
if (token.type === 'identifier') { | |
// Check for equals operator | |
if (i + 2 < tokens.length && tokens[i+1].type === 'equals') { | |
const key = token.value; | |
let value = tokens[i+2].value; | |
if (tokens[i+2].type === 'angle_expr') { | |
value = value; // Keep angle bracket content as is | |
} | |
properties[key] = value; | |
i += 2; | |
continue; | |
} | |
// Check for empty parentheses | |
if (i + 1 < tokens.length && tokens[i+1].type === 'empty_parens') { | |
if (i + 2 < tokens.length && tokens[i+2].type === 'empty_parens') { | |
properties[token.value] = '()()'; | |
i += 2; | |
} else { | |
properties[token.value] = '()'; | |
i++; | |
} | |
continue; | |
} | |
// Check for normal parentheses | |
if (i + 1 < tokens.length && tokens[i+1].type === 'lparen') { | |
current = token.value; | |
inParens = true; | |
parenContent = []; | |
i++; | |
continue; | |
} | |
} | |
if (token.type === 'number_range') { | |
properties.range = token.value; | |
continue; | |
} | |
if (token.type === 'hash' && | |
i + 3 < tokens.length && | |
tokens[i+1].type === 'identifier' && | |
tokens[i+2].type === 'equals') { | |
properties[tokens[i+1].value] = tokens[i+3].value; | |
i += 3; | |
continue; | |
} | |
if (inParens) { | |
if (token.type === 'rparen') { | |
properties[current] = parenContent; | |
current = null; | |
inParens = false; | |
} else if (token.type !== 'comma') { | |
if (token.type === 'column_ref' || | |
token.type === 'empty_column_ref' || | |
token.type === 'number' || | |
token.type === 'measure_ref') { | |
parenContent.push(token.value); | |
} else { | |
parenContent.push(token.value); | |
} | |
} | |
} | |
} | |
return properties; | |
} | |
%} | |
token -> | |
%operator_type {% id %} | |
| %identifier {% id %} | |
| %equals {% id %} | |
| %lparen {% id %} | |
| %rparen {% id %} | |
| %angle_expr {% id %} | |
| %number {% id %} | |
| %hash {% id %} | |
| %comma {% id %} | |
| %column_ref {% id %} | |
| %empty_column_ref {% id %} | |
| %empty_parens {% id %} | |
| %number_range {% id %} | |
| %keyword {% id %} | |
| %measure_ref {% id %} | |
_ -> %ws:* {% | |
function(d) { | |
return d[0].map(ws => ws.value).join(''); | |
} | |
%} | |
@{% | |
function buildTree(lines) { | |
const root = { children: [] }; | |
const stack = [root]; | |
let currentIndent = 0; | |
lines.forEach(line => { | |
while (line.indent < currentIndent) { | |
stack.pop(); | |
currentIndent--; | |
} | |
while (line.indent > currentIndent) { | |
const parent = stack[stack.length - 1]; | |
const newNode = { children: [] }; | |
parent.children.push(newNode); | |
stack.push(newNode); | |
currentIndent++; | |
} | |
const current = stack[stack.length - 1]; | |
const node = { | |
operator: line.operator, | |
parameters: line.parameters, | |
properties: line.properties, | |
children: [] | |
}; | |
current.children.push(node); | |
}); | |
return root.children; | |
} | |
%} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment