2019-11-14 13:38:38 +01:00
|
|
|
import { compileExpr, QWebVar } from "./expression_parser";
|
2019-07-14 22:18:00 +02:00
|
|
|
|
2019-09-21 09:28:56 +02:00
|
|
|
export const INTERP_REGEXP = /\{\{.*?\}\}/g;
|
2019-07-14 22:18:00 +02:00
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
// Compilation Context
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
2019-10-10 20:59:57 +02:00
|
|
|
export class CompilationContext {
|
2019-11-27 15:07:10 +01:00
|
|
|
static nextID: number = 1;
|
2019-07-14 22:18:00 +02:00
|
|
|
code: string[] = [];
|
|
|
|
|
variables: { [key: string]: QWebVar } = {};
|
|
|
|
|
escaping: boolean = false;
|
2019-12-07 22:56:00 +01:00
|
|
|
parentNode: number | null | string = null;
|
2019-07-14 22:18:00 +02:00
|
|
|
parentTextNode: number | null = null;
|
|
|
|
|
rootNode: number | null = null;
|
|
|
|
|
indentLevel: number = 0;
|
2019-10-10 20:59:57 +02:00
|
|
|
rootContext: CompilationContext;
|
2019-07-14 22:18:00 +02:00
|
|
|
shouldDefineParent: boolean = false;
|
2019-12-07 22:56:00 +01:00
|
|
|
shouldDefineScope: boolean = false;
|
2019-07-14 22:18:00 +02:00
|
|
|
shouldDefineQWeb: boolean = false;
|
|
|
|
|
shouldDefineUtils: boolean = false;
|
2019-09-26 21:56:01 +02:00
|
|
|
shouldDefineRefs: boolean = false;
|
2019-09-11 11:28:41 +02:00
|
|
|
shouldDefineResult: boolean = true;
|
2019-10-16 14:51:53 +02:00
|
|
|
loopNumber: number = 0;
|
2019-07-14 22:18:00 +02:00
|
|
|
inPreTag: boolean = false;
|
|
|
|
|
templateName: string;
|
|
|
|
|
allowMultipleRoots: boolean = false;
|
|
|
|
|
hasParentWidget: boolean = false;
|
2019-12-16 16:50:21 +01:00
|
|
|
hasKey0: boolean = false;
|
|
|
|
|
keyStack: boolean[] = [];
|
2019-07-14 22:18:00 +02:00
|
|
|
|
|
|
|
|
constructor(name?: string) {
|
|
|
|
|
this.rootContext = this;
|
|
|
|
|
this.templateName = name || "noname";
|
2019-12-12 22:27:52 +01:00
|
|
|
this.addLine("let h = this.h;");
|
2019-07-14 22:18:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
generateID(): number {
|
2019-11-27 15:07:10 +01:00
|
|
|
return CompilationContext.nextID++;
|
2019-07-14 22:18:00 +02:00
|
|
|
}
|
|
|
|
|
|
2019-11-18 17:19:11 +01:00
|
|
|
/**
|
|
|
|
|
* This method generates a "template key", which is basically a unique key
|
|
|
|
|
* which depends on the currently set keys, and on the iteration numbers (if
|
|
|
|
|
* we are in a loop).
|
|
|
|
|
*
|
|
|
|
|
* Such a key is necessary when we need to associate an id to some element
|
|
|
|
|
* generated by a template (for example, a component)
|
|
|
|
|
*/
|
2019-12-12 12:18:08 +01:00
|
|
|
generateTemplateKey(prefix: string = ""): string {
|
2019-11-18 17:19:11 +01:00
|
|
|
const id = this.generateID();
|
2019-12-16 16:50:21 +01:00
|
|
|
if (this.loopNumber === 0 && !this.hasKey0) {
|
2019-12-12 12:18:08 +01:00
|
|
|
return `'${prefix}__${id}__'`;
|
2019-12-12 09:24:07 +01:00
|
|
|
}
|
2019-12-16 16:50:21 +01:00
|
|
|
let key = `\`${prefix}__${id}__`;
|
|
|
|
|
let start = this.hasKey0 ? 0 : 1;
|
|
|
|
|
for (let i = start; i < this.loopNumber + 1; i++) {
|
|
|
|
|
key += `\${key${i}}__`;
|
2019-11-18 17:19:11 +01:00
|
|
|
}
|
2019-12-16 16:50:21 +01:00
|
|
|
this.addLine(`let k${id} = ${key}\`;`);
|
2019-11-18 17:19:11 +01:00
|
|
|
return `k${id}`;
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-14 22:18:00 +02:00
|
|
|
generateCode(): string[] {
|
|
|
|
|
if (this.shouldDefineResult) {
|
|
|
|
|
this.code.unshift(" let result;");
|
|
|
|
|
}
|
2019-12-07 22:56:00 +01:00
|
|
|
|
|
|
|
|
if (this.shouldDefineScope) {
|
|
|
|
|
this.code.unshift(" let scope = Object.create(context);");
|
|
|
|
|
}
|
2019-09-26 21:56:01 +02:00
|
|
|
if (this.shouldDefineRefs) {
|
|
|
|
|
this.code.unshift(" context.__owl__.refs = context.__owl__.refs || {};");
|
|
|
|
|
}
|
2019-07-14 22:18:00 +02:00
|
|
|
if (this.shouldDefineParent) {
|
|
|
|
|
if (this.hasParentWidget) {
|
|
|
|
|
this.code.unshift(" let parent = extra.parent;");
|
|
|
|
|
} else {
|
|
|
|
|
this.code.unshift(" let parent = context;");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (this.shouldDefineQWeb) {
|
|
|
|
|
this.code.unshift(" let QWeb = this.constructor;");
|
|
|
|
|
}
|
|
|
|
|
if (this.shouldDefineUtils) {
|
|
|
|
|
this.code.unshift(" let utils = this.constructor.utils;");
|
|
|
|
|
}
|
|
|
|
|
return this.code;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-10 20:59:57 +02:00
|
|
|
withParent(node: number): CompilationContext {
|
2019-07-14 22:18:00 +02:00
|
|
|
if (
|
|
|
|
|
!this.allowMultipleRoots &&
|
|
|
|
|
this === this.rootContext &&
|
|
|
|
|
(this.parentNode || this.parentTextNode)
|
|
|
|
|
) {
|
|
|
|
|
throw new Error("A template should not have more than one root node");
|
|
|
|
|
}
|
|
|
|
|
if (!this.rootContext.rootNode) {
|
|
|
|
|
this.rootContext.rootNode = node;
|
|
|
|
|
}
|
2019-10-11 12:44:21 +02:00
|
|
|
if (!this.parentNode && this.rootContext.shouldDefineResult) {
|
2019-09-11 14:01:49 +02:00
|
|
|
this.addLine(`result = vn${node};`);
|
2019-09-11 11:28:41 +02:00
|
|
|
}
|
2019-07-14 22:18:00 +02:00
|
|
|
return this.subContext("parentNode", node);
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-10 20:59:57 +02:00
|
|
|
subContext(key: keyof CompilationContext, value: any): CompilationContext {
|
2019-07-14 22:18:00 +02:00
|
|
|
const newContext = Object.create(this);
|
|
|
|
|
newContext[key] = value;
|
|
|
|
|
return newContext;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
indent() {
|
2019-12-16 16:50:21 +01:00
|
|
|
this.rootContext.indentLevel++;
|
2019-07-14 22:18:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dedent() {
|
2019-12-16 16:50:21 +01:00
|
|
|
this.rootContext.indentLevel--;
|
2019-07-14 22:18:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addLine(line: string): number {
|
|
|
|
|
const prefix = new Array(this.indentLevel + 2).join(" ");
|
|
|
|
|
this.code.push(prefix + line);
|
|
|
|
|
return this.code.length - 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addIf(condition: string) {
|
|
|
|
|
this.addLine(`if (${condition}) {`);
|
|
|
|
|
this.indent();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addElse() {
|
|
|
|
|
this.dedent();
|
|
|
|
|
this.addLine("} else {");
|
|
|
|
|
this.indent();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
closeIf() {
|
|
|
|
|
this.dedent();
|
|
|
|
|
this.addLine("}");
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-14 13:38:38 +01:00
|
|
|
getValue(val: any): QWebVar | string {
|
2019-07-14 22:18:00 +02:00
|
|
|
return val in this.variables ? this.getValue(this.variables[val]) : val;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Prepare an expression for being consumed at render time. Its main job
|
|
|
|
|
* is to
|
|
|
|
|
* - replace unknown variables by a lookup in the context
|
|
|
|
|
* - replace already defined variables by their internal name
|
|
|
|
|
*/
|
|
|
|
|
formatExpression(expr: string): string {
|
2019-12-07 22:56:00 +01:00
|
|
|
this.rootContext.shouldDefineScope = true;
|
2019-07-14 22:18:00 +02:00
|
|
|
return compileExpr(expr, this.variables);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Perform string interpolation on the given string. Note that if the whole
|
|
|
|
|
* string is an expression, it simply returns it (formatted and enclosed in
|
|
|
|
|
* parentheses).
|
|
|
|
|
* For instance:
|
|
|
|
|
* 'Hello {{x}}!' -> `Hello ${x}`
|
|
|
|
|
* '{{x ? 'a': 'b'}}' -> (x ? 'a' : 'b')
|
|
|
|
|
*/
|
|
|
|
|
interpolate(s: string): string {
|
2019-09-21 09:28:56 +02:00
|
|
|
let matches = s.match(INTERP_REGEXP);
|
2019-07-14 22:18:00 +02:00
|
|
|
if (matches && matches[0].length === s.length) {
|
|
|
|
|
return `(${this.formatExpression(s.slice(2, -2))})`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let r = s.replace(/\{\{.*?\}\}/g, s => "${" + this.formatExpression(s.slice(2, -2)) + "}");
|
|
|
|
|
return "`" + r + "`";
|
|
|
|
|
}
|
2019-12-10 16:39:30 +01:00
|
|
|
startProtectScope(): number {
|
|
|
|
|
const protectID = this.generateID();
|
|
|
|
|
this.rootContext.shouldDefineScope = true;
|
2019-12-12 22:27:52 +01:00
|
|
|
this.addLine(`let _origScope${protectID} = scope;`);
|
2019-12-10 16:39:30 +01:00
|
|
|
this.addLine(`scope = Object.assign(Object.create(context), scope);`);
|
|
|
|
|
return protectID;
|
|
|
|
|
}
|
|
|
|
|
stopProtectScope(protectID: number) {
|
|
|
|
|
this.addLine(`scope = _origScope${protectID};`);
|
|
|
|
|
}
|
2019-07-14 22:18:00 +02:00
|
|
|
}
|