From d1c2d4f4b82c4b0f3d0f8f7f87eea366e25bd60f Mon Sep 17 00:00:00 2001 From: Garrett Mills Date: Wed, 7 Oct 2026 17:41:45 -0500 Subject: [PATCH] Add exists command --- HELP.md | 6 +++++ src/vm/commands/exists.ts | 51 +++++++++++++++++++++++++++++++++++++++ src/vm/commands/index.ts | 2 ++ 3 files changed, 59 insertions(+) create mode 100644 src/vm/commands/exists.ts diff --git a/HELP.md b/HELP.md index 9ec023a..792357c 100644 --- a/HELP.md +++ b/HELP.md @@ -446,6 +446,12 @@ Assuming the current subject is a valid single data document, convert it `from` Supports: `php`|`json` Example: `{"a": 1}` -> `convert json php` -> `['a' => 1]` +#### `exists ` +Checks whether the given file or variable exists. +Supports: `fs` for filesystem checks, `var` for variable names +Example: `$a = 1` -> `exists var a` -> `1` +Example: `exists fs fake-file.txt` -> `0` + #### User-specific setup files The file `~/.str.rc` is automatically executed when `str` starts up. You can use this to define a user-specific environment. diff --git a/src/vm/commands/exists.ts b/src/vm/commands/exists.ts new file mode 100644 index 0000000..d37a9ce --- /dev/null +++ b/src/vm/commands/exists.ts @@ -0,0 +1,51 @@ +import {Command, ParseContext, processPath, StrLVal, StrTerm, wrapInt} from "./command.js"; +import {LexInput} from "../lexer.js"; +import {ExecutionError, StrVM} from "../vm.js"; +import {Awaitable} from "../../util/types.js"; +import * as fs from 'node:fs'; +import {log} from "../../log.js"; + +export type ExistsData = { + check: 'fs'|'var', + lexical: StrTerm, +} + +export class Exists extends Command { + async attemptParse(context: ParseContext): Promise { + return { + check: context.popKeywordInSet(['fs', 'var']).value, + lexical: await context.popTerm(), + } + } + + getDisplayName(): string { + return 'exists' + } + + isParseCandidate(token: LexInput): boolean { + return this.isKeyword(token, 'exists') + } + + execute(vm: StrVM, data: ExistsData): Awaitable { + const logger = log.getStreamLogger('cmd-exists') + + return vm.replaceContextMatchingTerm(ctx => ({ + override: async () => { + if ( data.check === 'fs' ) { + const path = processPath(ctx.resolveString(data.lexical)) + + logger.debug({ checkPath: path }) + return wrapInt(fs.existsSync(path) ? 1 : 0) + } else if ( data.check === 'var' ) { + const lval: StrLVal = { term: 'variable', name: '$' + ctx.resolveString(data.lexical) } + const val = ctx.resolve(lval) + + logger.debug({ checkVar: lval, val }) + return wrapInt(val ? 1 : 0) + } + + throw new ExecutionError(`Existence operator not implemented: ${data.check}`) + }, + })) + } +} diff --git a/src/vm/commands/index.ts b/src/vm/commands/index.ts index 16ba36f..fc98a43 100644 --- a/src/vm/commands/index.ts +++ b/src/vm/commands/index.ts @@ -66,6 +66,7 @@ import {Times} from "./times.js"; import {Divide} from "./divide.js"; import {Abs} from "./abs.js"; import {Rand} from "./rand.js"; +import {Exists} from "./exists.js"; export type Commands = Command[] export const commands: Commands = [ @@ -83,6 +84,7 @@ export const commands: Commands = [ new Each, new Edit, new Enclose, + new Exists, new Exit, new Flatten, new From,