Skip to content

Commit

Permalink
[IMP] compiler: add support for t-for directive
Browse files Browse the repository at this point in the history
This commit adds support for the `t-for` whose syntax and usage is
similar to the syntax of the for..of loop in JS (and in fact it compiles
to a for..of loop). This looping construct supports looping on arbitrary
iterables and destructuring assignments, which would be difficult to
support in a backward compatible manner on the existing t-foreach
directive.
  • Loading branch information
sdegueldre committed Jul 20, 2023
1 parent 9d99f89 commit 58240bd
Show file tree
Hide file tree
Showing 6 changed files with 1,957 additions and 2 deletions.
45 changes: 44 additions & 1 deletion src/compiler/code_generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
ASTTCallBlock,
ASTTEsc,
ASTText,
ASTTFor,
ASTTForEach,
ASTTif,
ASTTKey,
Expand Down Expand Up @@ -471,6 +472,8 @@ export class CodeGenerator {
return this.compileTIf(ast, ctx);
case ASTType.TForEach:
return this.compileTForeach(ast, ctx);
case ASTType.TFor:
return this.compileTFor(ast, ctx);
case ASTType.TKey:
return this.compileTKey(ast, ctx);
case ASTType.Multi:
Expand Down Expand Up @@ -907,7 +910,7 @@ export class CodeGenerator {
if (!ast.hasNoValue) {
this.addLine(`ctx[\`${ast.elem}_value\`] = ${keys}[${loopVar}];`);
}
this.define(`key${this.target.loopLevel}`, ast.key ? compileExpr(ast.key) : loopVar);
this.define(`key${this.target.loopLevel}`, compileExpr(ast.key));
if (this.dev) {
// Throw error on duplicate keys in dev mode
this.helpers.add("OwlError");
Expand Down Expand Up @@ -954,6 +957,46 @@ export class CodeGenerator {
return block.varName;
}

compileTFor(ast: ASTTFor, ctx: Context): string {
let { block } = ctx;
if (block) {
this.insertAnchor(block);
}
block = this.createBlock(block, "list", ctx);
this.target.loopLevel++;
this.addLine(`ctx = Object.create(ctx);`);
// Throw errors on duplicate keys in dev mode
if (this.dev) {
this.define(`keys${block.id}`, `new Set()`);
}
this.define(`c_block${block.id}`, "[]");
const index = `i${this.target.loopLevel}`;
this.addLine(`let ${index} = ${0};`);
const binding = compileExpr(ast.binding);
this.addLine(`for (${binding} of ${compileExpr(ast.iterable)}) {`);
this.target.indentLevel++;
this.define(`key${this.target.loopLevel}`, compileExpr(ast.key));
if (this.dev) {
// Throw error on duplicate keys in dev mode
this.helpers.add("OwlError");
this.addLine(
`if (keys${block.id}.has(String(key${this.target.loopLevel}))) { throw new OwlError(\`Got duplicate key in t-for: \${key${this.target.loopLevel}}\`)}`
);
this.addLine(`keys${block.id}.add(String(key${this.target.loopLevel}));`);
}
const subCtx = createContext(ctx, { block, index });
this.compileAST(ast.body, subCtx);
this.addLine(`${index}++;`);
this.target.indentLevel--;
this.target.loopLevel--;
this.addLine(`}`);
if (!ctx.isLast) {
this.addLine(`ctx = ctx.__proto__;`);
}
this.insertBlock("l", block, ctx);
return block.varName;
}

compileTKey(ast: ASTTKey, ctx: Context): string | null {
const tKeyExpr = generateId("tKey_");
this.define(tKeyExpr, compileExpr(ast.expr));
Expand Down
43 changes: 42 additions & 1 deletion src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const enum ASTType {
TCallBlock,
TTranslation,
TPortal,
TFor,
}

export interface ASTText {
Expand Down Expand Up @@ -104,7 +105,15 @@ export interface ASTTForEach {
hasNoLast: boolean;
hasNoIndex: boolean;
hasNoValue: boolean;
key: string | null;
key: string;
}

export interface ASTTFor {
type: ASTType.TFor;
iterable: string;
binding: string;
body: AST;
key: string;
}

export interface ASTTKey {
Expand Down Expand Up @@ -183,6 +192,7 @@ export type AST =
| ASTTCall
| ASTTOut
| ASTTForEach
| ASTTFor
| ASTTKey
| ASTComponent
| ASTSlot
Expand Down Expand Up @@ -230,6 +240,7 @@ function parseNode(node: Node, ctx: ParsingContext): AST | null {
return (
parseTDebugLog(node, ctx) ||
parseTForEach(node, ctx) ||
parseTFor(node, ctx) ||
parseTIf(node, ctx) ||
parseTPortal(node, ctx) ||
parseTCall(node, ctx) ||
Expand Down Expand Up @@ -531,6 +542,36 @@ function parseTForEach(node: Element, ctx: ParsingContext): AST | null {
};
}

function parseTFor(node: Element, ctx: ParsingContext): AST | null {
if (!node.hasAttribute("t-for")) {
return null;
}
const binding = node.getAttribute("t-for")!;
node.removeAttribute("t-for");
const iterable = node.getAttribute("t-of") || "";
node.removeAttribute("t-of");
const key = node.getAttribute("t-key");
if (!key) {
throw new OwlError(
`"Directive t-for should always be used with a t-key!" (expression: t-for="${binding}" t-of="${iterable}")`
);
}
node.removeAttribute("t-key");
const body = parseNode(node, ctx);

if (!body) {
return null;
}

return {
type: ASTType.TFor,
iterable,
binding,
body,
key,
};
}

function parseTKey(node: Element, ctx: ParsingContext): AST | null {
if (!node.hasAttribute("t-key")) {
return null;
Expand Down
Loading

0 comments on commit 58240bd

Please sign in to comment.