diff --git a/HELP.md b/HELP.md index b0641f0..7808df7 100644 --- a/HELP.md +++ b/HELP.md @@ -381,6 +381,14 @@ str %> call $myFooReplacer bar ``` +### Control Structures + +#### `if ` +Execute the given lambda if `cond` is truthy. Cond may be a lambda, in which case it is executed. +A value is truthy UNLESS it is the literal empty string (`''`) or the integer `0`. +Example: `foo` -> `if (contains foo) (suffix bar)` -> `foobar` + + ### Misc #### `lipsum ` diff --git a/src/vm/commands/if.ts b/src/vm/commands/if.ts new file mode 100644 index 0000000..2a8cc2a --- /dev/null +++ b/src/vm/commands/if.ts @@ -0,0 +1,56 @@ +import {LexInput} from "../lexer.js"; +import {Command, ParseContext, StrTerm} from "./command.js"; +import {StrVM} from "../vm.js"; +import {Call} from "./call.js"; + +export type IfData = { + cond: StrTerm, + callable: StrTerm, +} + +export class If extends Command { + async attemptParse(context: ParseContext): Promise { + return { + cond: await context.popTerm(), + callable: await context.popTerm(), + } + } + + isParseCandidate(token: LexInput): boolean { + return this.isKeyword(token, 'if') + } + + getDisplayName(): string { + return 'if' + } + + async execute(vm: StrVM, data: IfData): Promise { + return vm.replaceContextFromChild(async (childVM, ctx) => { + let cond = ctx.resolveRequired(data.cond) + + // If `cond` is a lambda, then call it before evaluating its truthiness. + if ( cond.term === 'lambda' ) { + await childVM.runInChild(async (lambdaVM, lambdaCtx) => { + await (new Call).execute(lambdaVM, { + callable: cond, + params: [], + }) + + cond = lambdaCtx.getSubject() + }) + } + + if ( (cond.term === 'string' || cond.term === 'int') && !cond.value ) { + // The string was empty/falsy, so do not execute the body. + return + } + + const callable = ctx.resolveLambda(data.callable) + + await (new Call).execute(childVM, { + callable, + params: [], + }) + }) + } +} diff --git a/src/vm/commands/index.ts b/src/vm/commands/index.ts index ca45ebb..ecebdbd 100644 --- a/src/vm/commands/index.ts +++ b/src/vm/commands/index.ts @@ -54,6 +54,7 @@ import {Script} from "./script.js"; import {Take} from "./take.js"; import {Group} from "./group.js"; import {Flatten} from "./flatten.js"; +import {If} from "./if.js"; export type Commands = Command[] export const commands: Commands = [ @@ -74,6 +75,7 @@ export const commands: Commands = [ new Group, new Help, new History, + new If, new Indent, new InFile, new Join,