2021-01-18 14:32:38 +00:00
|
|
|
import { Items } from './tokens.js';
|
2021-01-14 12:09:53 +00:00
|
|
|
import { Pos, startPos } from './position.js';
|
2021-01-25 21:16:52 +00:00
|
|
|
import { laxRead, LaxReadOptions } from './reader.js';
|
2021-01-14 12:09:53 +00:00
|
|
|
import * as M from './matcher.js';
|
|
|
|
|
|
|
|
const substPat = M.scope((o: { pos: Pos }) =>
|
|
|
|
M.seq(M.atom('$'),
|
|
|
|
M.seq(M.bind(o, 'pos', M.pos), M.group('{', M.end, { skipSpace: false }))));
|
|
|
|
|
|
|
|
export type Substitution = Items | string;
|
|
|
|
|
2021-01-25 21:16:52 +00:00
|
|
|
function toItems(readOptions: LaxReadOptions, s: Substitution, pos: Pos): Items {
|
|
|
|
return typeof s === 'string' ? laxRead(s, { ... readOptions, start: pos, synthetic: true }) : s;
|
2021-01-14 12:09:53 +00:00
|
|
|
}
|
|
|
|
|
2021-01-18 22:11:53 +00:00
|
|
|
export type TemplateFunction = (consts: TemplateStringsArray, ... vars: Substitution[]) => Items;
|
|
|
|
|
2021-01-14 12:09:53 +00:00
|
|
|
export class Templates {
|
|
|
|
readonly sources: { [name: string]: string } = {};
|
2021-01-18 22:11:53 +00:00
|
|
|
readonly defaultPos: Pos;
|
|
|
|
recordSources = false;
|
2021-01-25 21:16:52 +00:00
|
|
|
readonly readOptions: LaxReadOptions;
|
2021-01-18 22:11:53 +00:00
|
|
|
|
2021-01-25 21:16:52 +00:00
|
|
|
constructor(defaultPos: Pos = startPos(null), readOptions: LaxReadOptions = {}) {
|
2021-01-18 22:11:53 +00:00
|
|
|
this.defaultPos = defaultPos;
|
2021-01-25 21:16:52 +00:00
|
|
|
this.readOptions = readOptions;
|
2021-01-18 22:11:53 +00:00
|
|
|
}
|
2021-01-14 12:09:53 +00:00
|
|
|
|
2021-01-18 22:11:53 +00:00
|
|
|
template(start0: Pos | string = this.defaultPos): TemplateFunction {
|
2021-01-14 12:09:53 +00:00
|
|
|
const start = (typeof start0 === 'string') ? startPos(start0) : start0;
|
|
|
|
return (consts, ... vars) => {
|
|
|
|
const sourcePieces = [consts[0]];
|
|
|
|
for (let i = 1; i < consts.length; i++) {
|
|
|
|
sourcePieces.push('${}');
|
|
|
|
sourcePieces.push(consts[i]);
|
|
|
|
}
|
|
|
|
const source = sourcePieces.join('');
|
2021-01-18 22:11:53 +00:00
|
|
|
if (this.recordSources) {
|
|
|
|
if (start.name !== null) {
|
|
|
|
if (start.name in this.sources && this.sources[start.name] !== source) {
|
|
|
|
throw new Error(`Duplicate template name: ${start.name}`);
|
|
|
|
}
|
|
|
|
this.sources[start.name] = source;
|
2021-01-14 12:09:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
let i = 0;
|
2021-01-25 21:16:52 +00:00
|
|
|
return M.replace(laxRead(source, { ... this.readOptions,
|
|
|
|
start,
|
|
|
|
extraDelimiters:
|
|
|
|
(this.readOptions.extraDelimiters ?? '') + '$',
|
|
|
|
synthetic: true,
|
|
|
|
}),
|
2021-01-16 16:46:18 +00:00
|
|
|
substPat,
|
2021-01-25 21:16:52 +00:00
|
|
|
sub => toItems(this.readOptions, vars[i++], sub.pos));
|
2021-01-14 12:09:53 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
sourceFor(name: string): string | undefined {
|
|
|
|
return this.sources[name];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-25 21:16:52 +00:00
|
|
|
export function joinItems(itemss: Items[],
|
|
|
|
separator0: Substitution = '',
|
|
|
|
readOptions: LaxReadOptions = {}): Items
|
|
|
|
{
|
2021-01-14 12:09:53 +00:00
|
|
|
if (itemss.length === 0) return [];
|
2021-01-25 21:16:52 +00:00
|
|
|
const separator = toItems(readOptions, separator0, startPos(null));
|
|
|
|
const acc: Items = [... itemss[0]];
|
2021-01-14 12:09:53 +00:00
|
|
|
for (let i = 1; i < itemss.length; i++) {
|
|
|
|
acc.push(... separator, ... itemss[i]);
|
|
|
|
}
|
|
|
|
return acc;
|
|
|
|
}
|
|
|
|
|
2021-01-16 16:46:18 +00:00
|
|
|
export function commaJoin(itemss: Items[]): Items {
|
|
|
|
return joinItems(itemss, ', ');
|
|
|
|
}
|
|
|
|
|
|
|
|
export const anonymousTemplate = (new Templates()).template();
|
|
|
|
|
2021-01-14 12:09:53 +00:00
|
|
|
// const lib = new Templates();
|
|
|
|
// const t = (o: {xs: Items}) => lib.template('testTemplate')`YOYOYOYO ${o.xs}><`;
|
|
|
|
// console.log(t({xs: lib.template()`hello there`}));
|