import { HZEngineCore } from "../../index.js"; export function conditional(core: HZEngineCore) { // if ... [elif] ... [else] ... end if // syntax: // if // // [elif ] // // [else] // // end if // if statement start core.script.use((ctx, next) => { if ( ctx.slicedArgs[0].isQuoted || ctx.slicedArgs[0].str.toLowerCase() !== "if" ) { return next(); } let data = ctx.startStatement("if") as unknown as IfStatementData; // console.log(`if data=${JSON.stringify(data)}`); let if_expression_res: unknown = core.script.evalExpression( data.if_expression ); if (typeof if_expression_res !== "boolean") { throw `If statement: if expression must return boolean, at file [${data.start_position[0]}] line [${data.start_position[1]}]`; } if (if_expression_res) { // continue to execute next line return; } // analyse elif for (let i = 0; i < data.elif_list.length; i++) { let item = data.elif_list[i]; let elif_expression_res = core.script.evalExpression(item.expression); if (typeof elif_expression_res !== "boolean") { throw `If statement: elif expression must return boolean, at file [${item.position[0]}] line [${item.position[1]}]`; } if (elif_expression_res) { // jump to next line of elif statement core.script.jump(item.position[0], item.position[1] + 1); return; } } // analyse else if (data.else_position) { core.script.jump(data.else_position[0], data.else_position[1] + 1); return; } // the expressions are all false // jump to the end of if statement core.script.jump(data.end_position[0], data.end_position[1]); }); // elif statement // when come across next elif statement, jump to "end if" statement to stop execute. core.script.use((ctx, next) => { if ( ctx.slicedArgs[0].isQuoted || ctx.slicedArgs[0].str.toLowerCase() !== "elif" ) { return next(); } if ( ctx.statementStack.length === 0 || ctx.statementStack[ctx.statementStack.length - 1][0] !== "if" ) { throw `If statement: elif statement must come after if statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`; } let data = ctx.statementStack[ ctx.statementStack.length - 1 ][2] as unknown as IfStatementData; // check if this elif statement belong to the closest if statement for (let i = 0; i < data.elif_list.length; i++) { let item = data.elif_list[i]; if ( item.position[0] === ctx.currentPath && item.position[1] === ctx.currentLineIndex ) { // jump to the "end if" statement core.script.jump(data.end_position[0], data.end_position[1]); return; } } // the elif statement is not belong to the closest if statement, so throw error throw `If statement: elif statement not belong to the closest if statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`; }); // else statement core.script.use((ctx, next) => { if ( ctx.slicedArgs[0].isQuoted || ctx.slicedArgs[0].str.toLowerCase() !== "else" ) { return next(); } if ( ctx.statementStack.length === 0 || ctx.statementStack[ctx.statementStack.length - 1][0] !== "if" ) { throw `If statement: else statement must come after if statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`; } let data = ctx.statementStack[ ctx.statementStack.length - 1 ][2] as unknown as IfStatementData; // check if this else statement belong to the closest if statement if (data.else_position) { if ( data.else_position[0] === ctx.currentPath && data.else_position[1] === ctx.currentLineIndex ) { // jump to the "end if" statement core.script.jump(data.end_position[0], data.end_position[1]); return; } } // the else statement is not belong to the closest if statement, so throw error throw `If statement: else statement not belong to the closest if statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`; }); // end if statement core.script.use((ctx, next) => { if (ctx.rawtext.trim().split(/ +/).join(" ").toLowerCase() !== "end if") { return next(); } if ( ctx.statementStack.length === 0 || ctx.statementStack[ctx.statementStack.length - 1][0] !== "if" ) { throw `If statement: end if statement must come after if statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`; } ctx.endStatement("if"); }); // analyse statement // if statement core.script.useAnalyseStatement((ctx, next) => { if ( ctx.slicedArgs[0].isQuoted || ctx.slicedArgs[0].str.toLowerCase() !== "if" ) { return next(); } if (ctx.slicedArgs.length < 2) { throw `If statement: no expression specified, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`; } ctx.startStatement("if", { start_position: [ctx.currentPath, ctx.currentLineIndex], if_expression: ctx.rawtext.trim().slice(2).trim(), elif_list: [], else_position: null, }); }); // elif statement core.script.useAnalyseStatement((ctx, next) => { if ( ctx.slicedArgs[0].isQuoted || ctx.slicedArgs[0].str.toLowerCase() !== "elif" ) { return next(); } if (ctx.slicedArgs.length < 2) { throw `If statement: no expression specified, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`; } // check if the closest statement is if statement if ( ctx.statementStack.length === 0 || ctx.statementStack[ctx.statementStack.length - 1][0] !== "if" ) { throw `If statement: elif statement must come after if statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`; } let data = ctx.statementStack[ ctx.statementStack.length - 1 ][2] as unknown as IfStatementData; data.elif_list.push({ position: [ctx.currentPath, ctx.currentLineIndex], expression: ctx.rawtext.trim().slice(4).trim(), }); }); // else statement core.script.useAnalyseStatement((ctx, next) => { if ( ctx.slicedArgs[0].isQuoted || ctx.slicedArgs[0].str.toLowerCase() !== "else" ) { return next(); } // check if the closest statement is if statement if ( ctx.statementStack.length === 0 || ctx.statementStack[ctx.statementStack.length - 1][0] !== "if" ) { throw `If statement: else statement must come after if statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`; } let data = ctx.statementStack[ ctx.statementStack.length - 1 ][2] as unknown as IfStatementData; data.else_position = [ctx.currentPath, ctx.currentLineIndex]; }); // end if statement core.script.useAnalyseStatement((ctx, next) => { if (ctx.rawtext.trim().split(/ +/).join(" ").toLowerCase() !== "end if") { return next(); } let data = ctx.endStatement("if") as unknown as IfStatementData; data.end_position = [ctx.currentPath, ctx.currentLineIndex]; ctx.setStatementData(data, [...data.start_position]); }); // while ... end while // while statement start core.script.use((ctx, next) => { if ( ctx.slicedArgs[0].isQuoted || ctx.slicedArgs[0].str.toLowerCase() !== "while" ) { return next(); } let data = ctx.startStatement("while") as unknown as WhileStatementData; // test if expression return boolean let expression = data.while_expression; let expression_res = core.script.evalExpression(expression); if (typeof expression_res !== "boolean") { throw `While statement: while expression must return boolean, at file [${data.start_position[0]}] line [${data.start_position[1]}]`; } if (!expression_res) { ctx.endStatement("while"); core.script.jump(...data.end_position, false); core.script.incrementNextPosition(); } else { return; } }); // while statement end core.script.use((ctx, next) => { if ( ctx.rawtext.trim().split(/ +/).join(" ").toLowerCase() !== "end while" ) { return next(); } // check if the closest statement is while statement if ( ctx.statementStack.length === 0 || ctx.statementStack[ctx.statementStack.length - 1][0] !== "while" ) { throw `While statement: end while statement must come after while statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`; } // jump to while statement start line (loop) let data = ctx.statementStack[ ctx.statementStack.length - 1 ][2] as unknown as WhileStatementData; ctx.endStatement("while"); core.script.jump(...data.start_position, false); }); // analyse while statement start core.script.useAnalyseStatement((ctx, next) => { if ( ctx.slicedArgs[0].isQuoted || ctx.slicedArgs[0].str.toLowerCase() !== "while" ) { return next(); } let while_expression = ctx.rawtext.trim().slice(5).trim(); if (while_expression === "") { throw `While statement: no expression specified, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`; } ctx.startStatement("while", { start_position: [ctx.currentPath, ctx.currentLineIndex], while_expression, end_position: [ctx.currentPath, ctx.currentLineIndex], }); }); // analyse while statement end core.script.useAnalyseStatement((ctx, next) => { if ( ctx.rawtext.trim().split(/ +/).join(" ").toLowerCase() !== "end while" ) { return next(); } let data = ctx.endStatement("while") as unknown as WhileStatementData; data.end_position = [ctx.currentPath, ctx.currentLineIndex]; ctx.setStatementData(data, [...data.start_position]); }); // do ... end do until // break command core.script.use((ctx, next) => { if ( ctx.slicedArgs[0].isQuoted || ctx.slicedArgs.length !== 1 || ctx.slicedArgs[0].str.toLowerCase() !== "break" ) { return next(); } // find the closest breakable statement // (currently only while statement is breakable) //(debug) output stack // console.log("Stack="+JSON.stringify(ctx.statementStack)); let resIndex = -1 for (let i = ctx.statementStack.length - 1; i >= 0; i--) { if (ctx.statementStack[i][0] === "while") { resIndex = i; break; } } if (resIndex === -1) { throw `Break command: break statement must come after while statement, at file [${ctx.currentPath}] line [${ctx.currentLineIndex}]`; } let data = ctx.statementStack[resIndex][2] as unknown as WhileStatementData; core.debug.log(`Break command: break from ${ctx.statementStack[resIndex][0]} statement at file [${data.start_position[0]}] line [${data.start_position[1] + 1}]`); core.script.jump( ...data.end_position, false ); core.script.incrementNextPosition(); // clear statement stack from breakable statement ctx.statementStack.splice(resIndex + 1, ctx.statementStack.length - resIndex - 1); }); } interface IfStatementData { start_position: [path: string, index: number]; if_expression: string; elif_list: ElifListItem[]; else_position: [path: string, index: number] | null; end_position: [path: string, index: number]; } interface ElifListItem { position: [path: string, index: number]; expression: string; } interface WhileStatementData { start_position: [path: string, index: number]; while_expression: string; end_position: [path: string, index: number]; }