|
|
|
|
|
|
|
|
|
|
|
export const margin = 0; |
|
|
export const newline = 1; |
|
|
export const indent = 2; |
|
|
export const dedent = 3; |
|
|
export const space = 4; |
|
|
|
|
|
export class Context { |
|
|
#visitors; |
|
|
#commands; |
|
|
#has_newline = false; |
|
|
|
|
|
multiline = false; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
constructor(visitors, commands = []) { |
|
|
this.#visitors = visitors; |
|
|
this.#commands = commands; |
|
|
} |
|
|
|
|
|
indent() { |
|
|
this.#commands.push(indent); |
|
|
} |
|
|
|
|
|
dedent() { |
|
|
this.#commands.push(dedent); |
|
|
} |
|
|
|
|
|
margin() { |
|
|
this.#commands.push(margin); |
|
|
} |
|
|
|
|
|
newline() { |
|
|
this.#has_newline = true; |
|
|
this.#commands.push(newline); |
|
|
} |
|
|
|
|
|
space() { |
|
|
this.#commands.push(space); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
append(context) { |
|
|
this.#commands.push(context.#commands); |
|
|
|
|
|
if (this.#has_newline) { |
|
|
this.multiline = true; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
write(content, node) { |
|
|
if (node?.loc) { |
|
|
this.location(node.loc.start.line, node.loc.start.column); |
|
|
this.#commands.push(content); |
|
|
this.location(node.loc.end.line, node.loc.end.column); |
|
|
} else { |
|
|
this.#commands.push(content); |
|
|
} |
|
|
|
|
|
if (this.#has_newline) { |
|
|
this.multiline = true; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
location(line, column) { |
|
|
this.#commands.push({ type: 'Location', line, column }); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
visit(node) { |
|
|
const visitor = this.#visitors[node.type]; |
|
|
|
|
|
if (!visitor) { |
|
|
let message = `Not implemented: ${node.type}`; |
|
|
|
|
|
if (node.type.includes('TS')) { |
|
|
message += ` (consider using 'esrap/languages/ts')`; |
|
|
} |
|
|
|
|
|
if (node.type.includes('JSX')) { |
|
|
message += ` (consider using 'esrap/languages/tsx')`; |
|
|
} |
|
|
|
|
|
throw new Error(message); |
|
|
} |
|
|
|
|
|
if (this.#visitors._) { |
|
|
|
|
|
this.#visitors._(node, this, (node) => visitor(node, this)); |
|
|
} else { |
|
|
|
|
|
visitor(node, this); |
|
|
} |
|
|
} |
|
|
|
|
|
empty() { |
|
|
return !this.#commands.some(has_content); |
|
|
} |
|
|
|
|
|
measure() { |
|
|
return measure(this.#commands); |
|
|
} |
|
|
|
|
|
new() { |
|
|
return new Context(this.#visitors); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function measure(commands, from = 0, to = commands.length) { |
|
|
let total = 0; |
|
|
|
|
|
for (let i = from; i < to; i += 1) { |
|
|
const command = commands[i]; |
|
|
|
|
|
if (typeof command === 'string') { |
|
|
total += command.length; |
|
|
} else if (Array.isArray(command)) { |
|
|
total += measure(command); |
|
|
} |
|
|
} |
|
|
|
|
|
return total; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function has_content(command) { |
|
|
if (Array.isArray(command)) { |
|
|
return command.some(has_content); |
|
|
} |
|
|
|
|
|
if (typeof command === 'string') { |
|
|
return command.length > 0; |
|
|
} |
|
|
|
|
|
return false; |
|
|
} |
|
|
|