|
type Nested<T = unknown> = (T | Nested<T>)[]; |
|
|
|
function extract(tokens: string[]) { |
|
let i = 0; |
|
|
|
return (function main(): Nested<string> { |
|
const a = [] as Nested<string>; |
|
|
|
while (i < tokens.length) { |
|
const token = tokens[i++]; |
|
|
|
if (token.startsWith(")")) return token.length !== 1 ? a.concat(token.slice(1)) : a; |
|
|
|
if (token === "(") a.push(main()); |
|
|
|
if (["F", "L", "R", "p", "q", "P"].some((c) => token.startsWith(c)) || /^[0-9]+$/.test(token)) a.push(token); |
|
} |
|
|
|
return a; |
|
})(); |
|
} |
|
|
|
function expand(code: string) { |
|
const tokens = []; |
|
|
|
const regex = [ |
|
/^(F(?:[1-9][0-9]*)?)/, |
|
/^(L(?:[1-9][0-9]*)?)/, |
|
/^(R(?:[1-9][0-9]*)?)/, |
|
/(^\()/, |
|
/(^\)(?:[1-9][0-9]*)?)/, |
|
/^(p[0-9]+)/, |
|
/^(q)/, |
|
/^(P[0-9]+)/, |
|
]; |
|
|
|
while (code.length) { |
|
code = code.trim(); |
|
|
|
const match = regex.reduce((m, p) => m || code.match(p), undefined! as RegExpMatchArray); |
|
|
|
tokens.push(match[0]); |
|
|
|
code = code.slice(match[0].length); |
|
} |
|
|
|
const patterns = new Map<string, Nested<string>>(); |
|
|
|
let depth = 0; |
|
|
|
const resolve = (s: string | Nested<string>, i: number, a: Nested<string> & { skip?: number }): string | Nested<string> => { |
|
if (a.skip) return (a.skip--, undefined!); |
|
|
|
if (typeof s === "string") { |
|
if (s.startsWith("p")) { |
|
if (depth) throw ""; |
|
|
|
if (a.indexOf("q", i) < 0) throw ""; |
|
|
|
const id = s.slice(1); |
|
|
|
const p = a.slice(i, a.indexOf("q", i)); |
|
|
|
let del = p.length; |
|
|
|
if (typeof p[0] === "string" && p[0].startsWith("p")) (p.shift(), del++); |
|
if (typeof p[p.length - 1] === "string" && p[p.length - 1] === "q") (p.pop(), del--); |
|
|
|
a.skip = del - 1; |
|
|
|
if (patterns.has(id)) throw ""; |
|
|
|
patterns.set(id, p); |
|
|
|
return undefined!; |
|
} |
|
|
|
if (s === "q") throw ""; |
|
|
|
return s; |
|
} else { |
|
depth++; |
|
|
|
const _ = s.map(resolve).filter((n) => n); |
|
|
|
depth--; |
|
|
|
return _; |
|
} |
|
}; |
|
|
|
const tree = extract(tokens).map(resolve).filter((n) => n); |
|
|
|
const stack = [] as string[]; |
|
|
|
const compile = (s: string | Nested<string>, i: number, a: Nested<string>): string => { |
|
if (stack.length > 128) throw ""; |
|
|
|
if (typeof s === "string") { |
|
if (s.startsWith("P")) { |
|
if (!s.slice(1)) throw ""; |
|
|
|
const p = patterns.get(s.slice(1)); |
|
|
|
if (!p) throw ""; |
|
|
|
stack.push(s.slice(1)); |
|
|
|
const _ = p.map(compile).join(""); |
|
|
|
stack.pop(); |
|
|
|
return _; |
|
} |
|
|
|
if (["F", "L", "R"].some((c) => s.startsWith(c))) return s[0].repeat(Number(s.slice(1) === "" ? 1 : s.slice(1))); |
|
|
|
return ""; |
|
} else { |
|
if (/^[0-9]+$/.test(s[s.length - 1].toString())) return s.slice(0, -1).map(compile).join("").repeat(Number(s[s.length - 1])); |
|
|
|
return s.map(compile).join(""); |
|
} |
|
}; |
|
|
|
return tree.map(compile).join(""); |
|
} |
|
|
|
function execute(code: string) { |
|
code = expand(code); |
|
|
|
const visited = [[0, 0]] as [number, number][]; |
|
|
|
const cmds = code.split(""); |
|
|
|
const pos = [0, 0] as [number, number]; |
|
|
|
const dir = [1, 0] as [number, number]; |
|
|
|
cmds.forEach((c) => { |
|
if (c === "F") { |
|
pos[0] += dir[0]; |
|
pos[1] += dir[1]; |
|
|
|
visited.push([...pos]); |
|
} |
|
|
|
if (c === "L") { |
|
dir.reverse(); |
|
dir[1] *= dir[0] ? 1 : -1; |
|
} |
|
|
|
if (c === "R") { |
|
dir.reverse(); |
|
dir[0] *= dir[1] ? 1 : -1; |
|
} |
|
}); |
|
|
|
const x = Math.min(...visited.map(([x]) => x)); |
|
const y = Math.min(...visited.map(([, y]) => y)); |
|
|
|
visited.forEach((p) => { |
|
if (x < 0) p[0] += Math.abs(x); |
|
if (y < 0) p[1] += Math.abs(y); |
|
}); |
|
|
|
const w = Math.max(...visited.map(([x]) => x)); |
|
const h = Math.max(...visited.map(([, y]) => y)); |
|
|
|
return visited.reduce((b, [x, y]) => (b[y][x] = "*", b), Array.from({ length: h + 1 }, () => Array.from({ length: w + 1 }, () => " "))).map((l) => l.join("")).join("\r\n"); |
|
} |