Implement output/clipboard interfaces, stub implementations, and implement execute()s for Copy and Paste

This commit is contained in:
Garrett Mills 2026-02-10 00:06:16 -06:00
parent f36621c646
commit 06ff1b396f
5 changed files with 80 additions and 14 deletions

View File

@ -5,6 +5,7 @@ import {Lexer} from "./vm/lexer.js";
import {Parser} from "./vm/parser.js"; import {Parser} from "./vm/parser.js";
import {commands} from "./vm/commands/index.js"; import {commands} from "./vm/commands/index.js";
import {Executor} from "./vm/vm.js"; import {Executor} from "./vm/vm.js";
import {ConsoleDisplay, FakeClipboard, OutputManager} from "./vm/output.js";
const lifecycle = new Lifecycle() const lifecycle = new Lifecycle()
const input = new Input() const input = new Input()
@ -17,8 +18,13 @@ lexer.subscribe(token => log.verbose('token', token))
const parser = new Parser(commands, lexer) const parser = new Parser(commands, lexer)
parser.subscribe(exec => log.verbose('exec', exec)) parser.subscribe(exec => log.verbose('exec', exec))
const exec = new Executor(parser) const output: OutputManager = {
exec.subscribe(state => state.output()) display: new ConsoleDisplay,
clipboard: new FakeClipboard,
}
const exec = new Executor(output, parser)
exec.subscribe(state => state.outputSubject())
input.setupPrompt() input.setupPrompt()

View File

@ -1,5 +1,7 @@
import {Command, ParseContext} from "./command.js"; import {Command, ParseContext, unwrapString} from "./command.js";
import {LexInput} from "../lexer.js"; import {LexInput} from "../lexer.js";
import {StrVM} from "../vm.js";
import {Awaitable} from "../../util/types.js";
export class Copy extends Command<{}> { export class Copy extends Command<{}> {
isParseCandidate(token: LexInput): boolean { isParseCandidate(token: LexInput): boolean {
@ -13,4 +15,10 @@ export class Copy extends Command<{}> {
getDisplayName(): string { getDisplayName(): string {
return 'copy' return 'copy'
} }
execute(vm: StrVM): Awaitable<StrVM> {
return vm.tapInPlace(ctx =>
vm.withOutput(output =>
output.clipboard.overwrite(unwrapString(ctx.getSubject()))))
}
} }

View File

@ -1,5 +1,7 @@
import {Command, ParseContext} from "./command.js"; import {Command, ParseContext, wrapString} from "./command.js";
import {LexInput} from "../lexer.js"; import {LexInput} from "../lexer.js";
import {StrVM} from "../vm.js";
import {Awaitable} from "../../util/types.js";
export class Paste extends Command<{}> { export class Paste extends Command<{}> {
isParseCandidate(token: LexInput): boolean { isParseCandidate(token: LexInput): boolean {
@ -13,4 +15,12 @@ export class Paste extends Command<{}> {
getDisplayName(): string { getDisplayName(): string {
return 'paste' return 'paste'
} }
execute(vm: StrVM): Awaitable<StrVM> {
return vm.replaceContextMatchingTerm({
override: () =>
vm.withOutput(async output =>
wrapString(await output.clipboard.read()))
})
}
} }

View File

@ -1,4 +1,5 @@
import {StrRVal} from "./commands/command.js"; import {StrRVal} from "./commands/command.js";
import {Awaitable} from "../util/types.js";
export const getSubjectDisplay = (sub: StrRVal): string => { export const getSubjectDisplay = (sub: StrRVal): string => {
if ( sub.term === 'string' ) { if ( sub.term === 'string' ) {
@ -11,3 +12,39 @@ export const getSubjectDisplay = (sub: StrRVal): string => {
return JSON.stringify(sub.value, null, '\t') // fixme return JSON.stringify(sub.value, null, '\t') // fixme
} }
export type Display = {
showSubject(sub: StrRVal): Awaitable<unknown>
}
export class ConsoleDisplay implements Display {
showSubject(sub: StrRVal) {
console.log(`\n---------------\n${getSubjectDisplay(sub)}\n---------------\n`)
}
}
export class NullDisplay implements Display {
showSubject() {}
}
export type Clipboard = {
read(): Awaitable<string>
overwrite(sub: string): Awaitable<unknown>
}
export class FakeClipboard {
private val = ''
read() {
return this.val
}
overwrite(sub: string) {
this.val = sub
}
}
export type OutputManager = {
display: Display,
clipboard: Clipboard,
}

View File

@ -14,7 +14,7 @@ import {StreamLogger} from "../util/log.js";
import {Parser} from "./parser.js"; import {Parser} from "./parser.js";
import {log} from "../log.js"; import {log} from "../log.js";
import {Executable} from "./parse.js"; import {Executable} from "./parse.js";
import {getSubjectDisplay} from "./output.js"; import {getSubjectDisplay, OutputManager} from "./output.js";
export class Scope { export class Scope {
private entries: Record<string, StrRVal> = {} private entries: Record<string, StrRVal> = {}
@ -188,13 +188,16 @@ export class ExecutionContext {
} }
export class StrVM { export class StrVM {
public static make(): StrVM { public static make(output: OutputManager): StrVM {
return new StrVM( return new StrVM(
new ExecutionContext(wrapString(''), new Scope())) new ExecutionContext(wrapString(''), new Scope()),
output,
)
} }
constructor( constructor(
private context: ExecutionContext, private context: ExecutionContext,
private output: OutputManager,
) {} ) {}
public async runInPlace<TReturn>(operator: (ctx: ExecutionContext) => Awaitable<TReturn>): Promise<TReturn> { public async runInPlace<TReturn>(operator: (ctx: ExecutionContext) => Awaitable<TReturn>): Promise<TReturn> {
@ -230,28 +233,30 @@ export class StrVM {
}))) })))
} }
output() { public async withOutput<TReturn>(operator: (output: OutputManager) => Awaitable<TReturn>): Promise<TReturn> {
console.log('---------------') return operator(this.output)
console.log(getSubjectDisplay(this.context.getSubject()))
console.log('---------------')
} }
makeChild(): StrVM { makeChild(): StrVM {
return new StrVM(this.context.makeChild()) return new StrVM(this.context.makeChild(), this.output)
}
async outputSubject(): Promise<void> {
await this.output.display.showSubject(this.context.getSubject())
} }
} }
export class Executor extends BehaviorSubject<StrVM> { export class Executor extends BehaviorSubject<StrVM> {
private logger: StreamLogger private logger: StreamLogger
constructor(parser?: Parser) { constructor(private output: OutputManager, parser?: Parser) {
super() super()
this.logger = log.getStreamLogger('executor') this.logger = log.getStreamLogger('executor')
parser?.subscribe(exec => this.handleExecutable(exec)) parser?.subscribe(exec => this.handleExecutable(exec))
} }
async handleExecutable(exec: Executable<CommandData>) { async handleExecutable(exec: Executable<CommandData>) {
const vm = this.currentValue || StrVM.make() const vm = this.currentValue || StrVM.make(this.output)
await this.next(await exec.command.execute(vm, exec.data)) await this.next(await exec.command.execute(vm, exec.data))
} }
} }