Skip to content

Instantly share code, notes, and snippets.

@brecert
Last active January 25, 2023 22:40
Show Gist options
  • Save brecert/ad983cb96daf1e415e707b3fd3de888a to your computer and use it in GitHub Desktop.
Save brecert/ad983cb96daf1e415e707b3fd3de888a to your computer and use it in GitHub Desktop.
mini-parser.js
const TOKENS =
/(?<name>[\w_-]+)|(?:"(?<string>.+?)")|(?<whitespace>\s+)|(?<symbol>.)/g;
type Token = {
name?: string;
string?: string;
whitespace?: string;
symbol?: string;
};
type Node = {
name: unknown;
attrs: unknown[][];
children: unknown[];
};
const lex = (string: string) =>
Array.from(string.matchAll(TOKENS), (m) => m.groups as Token)
.filter((t) => !t.whitespace);
class FragmentedDocumentParser {
strings: Token[][];
values: unknown[];
stringsPos = 0;
tokenPos = 0;
constructor(strings: TemplateStringsArray, values: unknown[]) {
this.strings = strings.map(lex);
this.values = values;
}
endOfStrings() {
return this.stringsPos >= this.values.length &&
this.tokenPos >= this.strings[this.stringsPos].length;
}
peekToken(): Token | undefined {
return this.strings[this.stringsPos][this.tokenPos];
}
readValue(): string | unknown {
const value = this.peekToken();
if (value) {
this.tokenPos += 1;
return Object.values(value).filter((_) => _)[0];
} else {
this.tokenPos = 0;
return this.values[this.stringsPos++];
}
}
parseAttrs() {
const attrs = [];
while (!this.endOfStrings() && this.peekToken()?.symbol !== "{") {
const name = this.readValue();
let value;
if (this.peekToken()?.symbol === "=") {
this.tokenPos += 1;
value = this.readValue();
}
attrs.push([name, value]);
}
return attrs;
}
parseBlock() {
if (this.peekToken()?.symbol !== "{") throw "Invalid Token";
this.tokenPos += 1;
const nodes = this.parseNodes();
this.tokenPos += 1;
return nodes;
}
parseNode(): Node {
const name = this.readValue();
const attrs = this.parseAttrs();
const children = this.parseBlock();
return { name, attrs, children };
}
parseNodes() {
let nodes: unknown[] = [];
while (!this.endOfStrings() && this.peekToken()?.symbol !== "}") {
if (this.peekToken() == null) {
nodes = nodes.concat(this.readValue());
} else {
nodes.push(this.parseNode());
}
}
return nodes;
}
}
type FragmentedNode<K, V, C> = {
name: string;
attrs: [K, V][];
children: (C | FragmentedNode<K, V, C>)[];
};
// syntax:
// root = nodes
// node = value attr+ '{' nodes '}'
// attr = value ('=' value)?
// name = [\w_-]+
// nodes = (node | $input)*
// value = string | name | $input
export const ytl = (
sources: TemplateStringsArray,
...values: unknown[]
) => new FragmentedDocumentParser(sources, values).parseNodes();
const id = "id";
const que = "que";
const children: unknown[] = [1, 2, 3];
const output = ytl`
div {
input id=${id} type="radio" name=${que} required {}
label for=${id} class=${"input-label"} {
${[children]}
}
}
`;
console.log(Deno.inspect(output, { colors: true, depth: Infinity }));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment