Created
October 12, 2022 22:27
-
-
Save panzi/d4b14498a4adad68663e798c9deb823d to your computer and use it in GitHub Desktop.
A very simple pre-processor that I use in some build step. Could be more efficient and feature rich, but that is not the scope of this.
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
/** | |
* Very simple pre-processor. | |
* | |
* Syntax: | |
* | |
* #if FLAG | |
* ... | |
* #elif OTHER_FLAG | |
* ... | |
* #else | |
* ... | |
* #endif | |
* | |
* Simple negation is also supported: | |
* | |
* #if !FLAG | |
* ... | |
* #endif | |
* | |
* Nesting is also supported: | |
* | |
* #if FLAG | |
* #if OTHER_FLAG | |
* ... | |
* #endif | |
* #endif | |
* | |
* @param {string} input | |
* @param {{ [flag: string]: boolean }} flags | |
* @returns string | |
*/ | |
function preproc(input, flags) { | |
const lines = input.split('\n'); | |
function parseInstr(line) { | |
const match = /^#\s*([_a-zA-Z]+\b)(.*)$/.exec(line); | |
if (!match) { | |
return null; | |
} | |
return [match[1], match[2].trim()]; | |
} | |
function parseFlag(arg) { | |
if (!arg) { | |
return null; | |
} | |
const match = /^\s*(!\s*)?([_a-zA-Z][_a-zA-Z0-9]*)\s*$/.exec(arg); | |
if (!match) { | |
return null; | |
} | |
return [!!match[1], match[2]]; | |
} | |
function process(index) { | |
const buf = []; | |
while (index < lines.length) { | |
const line = lines[index]; | |
if (line.startsWith('#')) { | |
const instr = parseInstr(line); | |
if (!instr) { | |
throw new Error(`syntax error on line ${index + 1}: ${line}`); | |
} | |
const [instrName, arg] = instr; | |
switch (instrName) { | |
case 'if': | |
const flag = parseFlag(arg); | |
if (!flag) { | |
throw new Error(`syntax error on line ${index + 1}: ${line}`); | |
} | |
const [notFlag, flagName] = flag; | |
const flagValue = flags[flagName]; | |
let matched = false; | |
const [nextIndex, body] = process(index + 1); | |
if (notFlag ? !flagValue : flagValue) { | |
matched = true; | |
buf.push(body); | |
} | |
index = nextIndex; | |
while (index < lines.length) { | |
const line = lines[index]; | |
const instr = parseInstr(line); | |
if (!instr) { | |
throw new Error(`syntax error on line ${index + 1}: ${line}`); | |
} | |
const [instrName, arg] = instr; | |
if (instrName !== 'elif') { | |
break; | |
} | |
const flag = parseFlag(arg); | |
if (!flag) { | |
throw new Error(`syntax error on line ${index + 1}: ${line}`); | |
} | |
const [notFlag, flagName] = flag; | |
const flagValue = flags[flagName]; | |
const [nextIndex, body] = process(index + 1); | |
if (!matched && (notFlag ? !flagValue : flagValue)) { | |
matched = true; | |
buf.push(body); | |
} | |
index = nextIndex; | |
} | |
{ | |
if (index >= lines.length) { | |
throw new Error(`syntax error on line ${index + 1}: unexpected end of input`); | |
} | |
let line = lines[index]; | |
let instr = parseInstr(line); | |
if (!instr) { | |
throw new Error(`syntax error on line ${index + 1}: ${line}`); | |
} | |
let [instrName, arg] = instr; | |
if (instrName === 'else') { | |
if (arg) { | |
throw new Error(`syntax error on line ${index + 1}: ${line}`); | |
} | |
const [nextIndex, body] = process(index + 1); | |
if (!matched) { | |
matched = true; | |
buf.push(body); | |
} | |
index = nextIndex; | |
if (index >= lines.length) { | |
throw new Error(`syntax error on line ${index + 1}: unexpected end of input`); | |
} | |
line = lines[index]; | |
instr = parseInstr(line); | |
if (!instr) { | |
throw new Error(`syntax error on line ${index + 1}: ${line}`); | |
} | |
[instrName, arg] = instr; | |
} | |
if (instrName !== 'endif') { | |
throw new Error(`syntax error on line ${index + 1}: ${line}`); | |
} | |
++ index; | |
} | |
break; | |
case 'elif': | |
case 'else': | |
case 'endif': | |
return [index, buf.join('\n')]; | |
default: | |
throw new Error(`syntax error on line ${index + 1}: ${line}`); | |
} | |
} else { | |
buf.push(line); | |
++ index; | |
} | |
} | |
return [index, buf.join('\n')]; | |
} | |
if (lines.length === 0) { | |
return ''; | |
} | |
const [index, output] = process(0); | |
if (index < lines.length) { | |
throw new Error(`syntax error on line ${index + 1}: ${lines[index]}`); | |
} | |
return output; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment