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 {commands} from "./vm/commands/index.js";
import {Executor} from "./vm/vm.js";
import {ConsoleDisplay, FakeClipboard, OutputManager} from "./vm/output.js";
const lifecycle = new Lifecycle()
const input = new Input()
@ -17,8 +18,13 @@ lexer.subscribe(token => log.verbose('token', token))
const parser = new Parser(commands, lexer)
parser.subscribe(exec => log.verbose('exec', exec))
const exec = new Executor(parser)
exec.subscribe(state => state.output())
const output: OutputManager = {
display: new ConsoleDisplay,
clipboard: new FakeClipboard,
}
const exec = new Executor(output, parser)
exec.subscribe(state => state.outputSubject())
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 {StrVM} from "../vm.js";
import {Awaitable} from "../../util/types.js";
export class Copy extends Command<{}> {
isParseCandidate(token: LexInput): boolean {
@ -13,4 +15,10 @@ export class Copy extends Command<{}> {
getDisplayName(): string {
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 {StrVM} from "../vm.js";
import {Awaitable} from "../../util/types.js";
export class Paste extends Command<{}> {
isParseCandidate(token: LexInput): boolean {
@ -13,4 +15,12 @@ export class Paste extends Command<{}> {
getDisplayName(): string {
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 {Awaitable} from "../util/types.js";
export const getSubjectDisplay = (sub: StrRVal): string => {
if ( sub.term === 'string' ) {
@ -11,3 +12,39 @@ export const getSubjectDisplay = (sub: StrRVal): string => {
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 {log} from "../log.js";
import {Executable} from "./parse.js";
import {getSubjectDisplay} from "./output.js";
import {getSubjectDisplay, OutputManager} from "./output.js";
export class Scope {
private entries: Record<string, StrRVal> = {}
@ -188,13 +188,16 @@ export class ExecutionContext {
}
export class StrVM {
public static make(): StrVM {
public static make(output: OutputManager): StrVM {
return new StrVM(
new ExecutionContext(wrapString(''), new Scope()))
new ExecutionContext(wrapString(''), new Scope()),
output,
)
}
constructor(
private context: ExecutionContext,
private output: OutputManager,
) {}
public async runInPlace<TReturn>(operator: (ctx: ExecutionContext) => Awaitable<TReturn>): Promise<TReturn> {
@ -230,28 +233,30 @@ export class StrVM {
})))
}
output() {
console.log('---------------')
console.log(getSubjectDisplay(this.context.getSubject()))
console.log('---------------')
public async withOutput<TReturn>(operator: (output: OutputManager) => Awaitable<TReturn>): Promise<TReturn> {
return operator(this.output)
}
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> {
private logger: StreamLogger
constructor(parser?: Parser) {
constructor(private output: OutputManager, parser?: Parser) {
super()
this.logger = log.getStreamLogger('executor')
parser?.subscribe(exec => this.handleExecutable(exec))
}
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))
}
}