import {log} from './log.js' import {Lifecycle} from './util/lifecycle.js' import {Input} from './vm/input.js' 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, OutputManager, WlClipboard} from "./vm/output.js"; import {processPath, unwrapString} from "./vm/commands/command.js"; import * as fs from "node:fs"; ;(async () => { const lifecycle = new Lifecycle() // Setup the input reader & logging: const input = new Input() input.adoptLifecycle(lifecycle) input.subscribe(line => log.verbose('input', { line })) // Chain on the lexer: const lexer = new Lexer(input) lexer.subscribe(token => log.verbose('token', token)) // Chain on the parser: const parser = new Parser(commands, lexer) parser.subscribe(exec => log.verbose('exec', exec)) // Chain on the VM executor: const output: OutputManager = { display: new ConsoleDisplay, clipboard: new WlClipboard, } const exec = new Executor(output, parser, input) exec.adoptLifecycle(lifecycle) // A little bit of window dressing: console.log('`str` : An interactive string manipulation environment') console.log('Copyright (C) 2026 Garrett Mills ') console.log('') // If the user has an rc-file, execute it: const rcFile = processPath('~/.str.rc') if ( fs.existsSync(rcFile) ) { log.verbose('rc', { rcFile }) const rcFileContent = fs.readFileSync(rcFile).toString() await input.pushLines('\n' + rcFileContent) console.log('Successfully loaded ~/.str.rc\n') } // If the user specified a filepath, load it as the initial subject: const editingFile: string|undefined = process.argv[2] if ( editingFile ) { log.debug('rc', { editingFile }) if ( !fs.existsSync(editingFile) ) { log.error('rc', 'Could not open file: ' + editingFile) process.exit(1) } const editingFileContent = fs.readFileSync(editingFile).toString() log.info('rc', 'Read file: ' + editingFile) log.verbose('rc', { editingFileContent }) await exec.tapVM(async vm => { await vm.replaceContextMatchingTerm({ override: editingFileContent, }) await vm.outputSubject() }) } // Print the subject after each command: exec.subscribe(state => state.outputSubject()) // Start the prompt: input.setupPrompt() // If we were editing a file, save the contents on close: lifecycle.onClose(async () => { log.debug('rc', { cleanupAndExit: true, editingFile }) if ( editingFile ) { await exec.tapVM(vm => vm.tapInPlace(ctx => { const editingFileOutputContent = unwrapString(ctx.getSubject()) log.verbose('rc', { editingFileOutputContent }) fs.writeFileSync(editingFile, editingFileOutputContent) log.info('rc', 'Wrote file: ' + editingFile) }) ) } }) process.on('SIGINT', () => lifecycle.close()) process.on('SIGTERM', () => lifecycle.close()) process.on('SIGQUIT', () => lifecycle.close()) })()