Skip to content

Instantly share code, notes, and snippets.

@Hugoberry
Last active January 29, 2025 14:06
Show Gist options
  • Save Hugoberry/7fcfddfdb64357791cf2524677924c12 to your computer and use it in GitHub Desktop.
Save Hugoberry/7fcfddfdb64357791cf2524677924c12 to your computer and use it in GitHub Desktop.
@{%
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