diff --git a/HELP.md b/HELP.md index 7808df7..276336a 100644 --- a/HELP.md +++ b/HELP.md @@ -388,6 +388,11 @@ Execute the given lambda if `cond` is truthy. Cond may be a lambda, in which cas A value is truthy UNLESS it is the literal empty string (`''`) or the integer `0`. Example: `foo` -> `if (contains foo) (suffix bar)` -> `foobar` +#### `unless ` +Inversion of `if`. +Example: `foo` -> `unless (contains foo) (suffix bar)` -> `foo` + + ### Misc diff --git a/src/vm/commands/index.ts b/src/vm/commands/index.ts index ecebdbd..7fea161 100644 --- a/src/vm/commands/index.ts +++ b/src/vm/commands/index.ts @@ -55,6 +55,7 @@ import {Take} from "./take.js"; import {Group} from "./group.js"; import {Flatten} from "./flatten.js"; import {If} from "./if.js"; +import {Unless} from "./unless.js"; export type Commands = Command[] export const commands: Commands = [ @@ -109,6 +110,7 @@ export const commands: Commands = [ new Trim, new Undo, new Unique, + new Unless, new Unquote, new Upper, new Word, diff --git a/src/vm/commands/unless.ts b/src/vm/commands/unless.ts new file mode 100644 index 0000000..5f88f56 --- /dev/null +++ b/src/vm/commands/unless.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 UnlessData = { + cond: StrTerm, + callable: StrTerm, +} + +export class Unless extends Command { + async attemptParse(context: ParseContext): Promise { + return { + cond: await context.popTerm(), + callable: await context.popTerm(), + } + } + + isParseCandidate(token: LexInput): boolean { + return this.isKeyword(token, 'unless') + } + + getDisplayName(): string { + return 'unless' + } + + async execute(vm: StrVM, data: UnlessData): Promise { + return vm.replaceContextFromChild(async (childVM, ctx) => { + let cond = ctx.resolveRequired(data.cond) + + // Unless `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 truthy, so do not execute the body. + return + } + + const callable = ctx.resolveLambda(data.callable) + + await (new Call).execute(childVM, { + callable, + params: [], + }) + }) + } +}