bun run repl
This commit is contained in:
parent
d306d58b2f
commit
6b9254c1c1
251
bin/repl
Executable file
251
bin/repl
Executable file
|
|
@ -0,0 +1,251 @@
|
|||
#!/usr/bin/env bun
|
||||
|
||||
import { Compiler } from '../src/compiler/compiler'
|
||||
import { VM, type Value, Scope } from 'reefvm'
|
||||
import * as readline from 'node:readline'
|
||||
|
||||
const colors = {
|
||||
reset: '\x1b[0m',
|
||||
bright: '\x1b[1m',
|
||||
dim: '\x1b[2m',
|
||||
cyan: '\x1b[36m',
|
||||
yellow: '\x1b[33m',
|
||||
green: '\x1b[32m',
|
||||
red: '\x1b[31m',
|
||||
blue: '\x1b[34m',
|
||||
magenta: '\x1b[35m',
|
||||
}
|
||||
|
||||
async function repl() {
|
||||
const commands = ['/clear', '/reset', '/vars', '/history', '/exit', '/quit']
|
||||
|
||||
function completer(line: string): [string[], string] {
|
||||
if (line.startsWith('/')) {
|
||||
const hits = commands.filter(cmd => cmd.startsWith(line))
|
||||
return [hits.length ? hits : commands, line]
|
||||
}
|
||||
return [[], line]
|
||||
}
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
prompt: `${colors.green}>>${colors.reset} `,
|
||||
completer,
|
||||
})
|
||||
|
||||
let codeHistory: string[] = []
|
||||
let lastVm: VM | null = null
|
||||
|
||||
showWelcome()
|
||||
|
||||
rl.prompt()
|
||||
|
||||
rl.on('line', async (line: string) => {
|
||||
const trimmed = line.trim()
|
||||
|
||||
if (!trimmed) {
|
||||
rl.prompt()
|
||||
return
|
||||
}
|
||||
|
||||
if (['/exit', 'exit', '/quit', 'quit'].includes(trimmed)) {
|
||||
console.log(`\n${colors.yellow}Goodbye!${colors.reset}`)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
if (trimmed === '/clear') {
|
||||
codeHistory = []
|
||||
lastVm = null
|
||||
console.clear()
|
||||
showWelcome()
|
||||
rl.prompt()
|
||||
return
|
||||
}
|
||||
|
||||
if (trimmed === '/reset') {
|
||||
codeHistory = []
|
||||
lastVm = null
|
||||
console.log(`\n${colors.yellow}State reset${colors.reset}`)
|
||||
rl.prompt()
|
||||
return
|
||||
}
|
||||
|
||||
if (trimmed === '/vars') {
|
||||
const varsVm = lastVm || new VM({ instructions: [], constants: [] }, nativeFunctions)
|
||||
console.log(`\n${colors.bright}Variables:${colors.reset}`)
|
||||
console.log(formatVariables(varsVm.scope))
|
||||
rl.prompt()
|
||||
return
|
||||
}
|
||||
|
||||
if (trimmed === '/history') {
|
||||
if (codeHistory.length === 0) {
|
||||
console.log(`\n${colors.dim}No history yet${colors.reset}`)
|
||||
} else {
|
||||
console.log(`\n${colors.bright}History:${colors.reset}`)
|
||||
codeHistory.forEach((code, i) => {
|
||||
console.log(`${colors.dim}[${i + 1}]${colors.reset} ${code}`)
|
||||
})
|
||||
}
|
||||
rl.prompt()
|
||||
return
|
||||
}
|
||||
|
||||
codeHistory.push(trimmed)
|
||||
|
||||
try {
|
||||
const fullCode = codeHistory.join('\n')
|
||||
const compiler = new Compiler(fullCode)
|
||||
|
||||
const vm = new VM(compiler.bytecode, nativeFunctions)
|
||||
const result = await vm.run()
|
||||
lastVm = vm
|
||||
|
||||
console.log(`${colors.dim}=>${colors.reset} ${formatValue(result)}`)
|
||||
} catch (error: any) {
|
||||
console.log(`\n${colors.red}Error:${colors.reset} ${error.message}`)
|
||||
codeHistory.pop()
|
||||
}
|
||||
|
||||
rl.prompt()
|
||||
})
|
||||
|
||||
rl.on('close', () => {
|
||||
console.log(`\n${colors.yellow}Goodbye!${colors.reset}`)
|
||||
process.exit(0)
|
||||
})
|
||||
|
||||
rl.on('SIGINT', () => {
|
||||
rl.write(null, { ctrl: true, name: 'u' })
|
||||
console.log('\n')
|
||||
rl.prompt()
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function formatValue(value: Value): string {
|
||||
switch (value.type) {
|
||||
case 'string':
|
||||
return `${colors.green}"${value.value}"${colors.reset}`
|
||||
case 'number':
|
||||
return `${colors.cyan}${value.value}${colors.reset}`
|
||||
case 'boolean':
|
||||
return `${colors.yellow}${value.value}${colors.reset}`
|
||||
case 'null':
|
||||
return `${colors.dim}null${colors.reset}`
|
||||
case 'array': {
|
||||
const items = value.value.map((v) => formatValue(v)).join(', ')
|
||||
return `${colors.blue}[${items}]${colors.reset}`
|
||||
}
|
||||
case 'dict': {
|
||||
const entries = Array.from(value.value.entries())
|
||||
.map(([k, v]) => `${k}: ${formatValue(v)}`)
|
||||
.join(', ')
|
||||
return `${colors.magenta}{${entries}}${colors.reset}`
|
||||
}
|
||||
case 'function': {
|
||||
const params = value.params.join(', ')
|
||||
return `${colors.dim}<fn(${params})>${colors.reset}`
|
||||
}
|
||||
case 'native':
|
||||
return `${colors.dim}<native-fn>${colors.reset}`
|
||||
case 'regex':
|
||||
return `${colors.magenta}${value.value}${colors.reset}`
|
||||
default:
|
||||
return String(value)
|
||||
}
|
||||
}
|
||||
|
||||
function formatVariables(scope: Scope): string {
|
||||
const vars: string[] = []
|
||||
|
||||
function collectVars(s: any, depth = 0) {
|
||||
if (!s) return
|
||||
|
||||
const prefix = depth > 0 ? `${colors.dim}(parent)${colors.reset} ` : ''
|
||||
|
||||
for (const [name, value] of s.locals.entries()) {
|
||||
vars.push(` ${prefix}${colors.bright}${name}${colors.reset} = ${formatValue(value)}`)
|
||||
}
|
||||
|
||||
if (s.parent) {
|
||||
collectVars(s.parent, depth + 1)
|
||||
}
|
||||
}
|
||||
|
||||
collectVars(scope)
|
||||
|
||||
if (vars.length === 0) {
|
||||
return ` ${colors.dim}[no variables]${colors.reset}`
|
||||
}
|
||||
|
||||
return vars.join('\n')
|
||||
}
|
||||
|
||||
function showWelcome() {
|
||||
console.log(
|
||||
`${colors.bright}${colors.cyan}═══════════════════════════════════════════════════════════════${colors.reset}`
|
||||
)
|
||||
console.log(`${colors.bright}🦐 Shrimp REPL${colors.reset}`)
|
||||
console.log(
|
||||
`${colors.bright}${colors.cyan}═══════════════════════════════════════════════════════════════${colors.reset}`
|
||||
)
|
||||
console.log(`\nType Shrimp expressions. Press ${colors.bright}Ctrl+D${colors.reset} to exit.`)
|
||||
console.log(`\nCommands:`)
|
||||
console.log(` ${colors.bright}/clear${colors.reset} - Clear screen and reset state`)
|
||||
console.log(` ${colors.bright}/reset${colors.reset} - Reset state (keep history visible)`)
|
||||
console.log(` ${colors.bright}/vars${colors.reset} - Show all variables`)
|
||||
console.log(` ${colors.bright}/history${colors.reset} - Show code history`)
|
||||
console.log(` ${colors.bright}/exit${colors.reset} - Quit REPL`)
|
||||
console.log(`\nExamples:`)
|
||||
console.log(` ${colors.cyan}5 + 10${colors.reset}`)
|
||||
console.log(` ${colors.cyan}x = 42${colors.reset}`)
|
||||
console.log(` ${colors.cyan}echo "Hello, world!"${colors.reset}`)
|
||||
console.log(` ${colors.cyan}greet = do name: echo "Hello" name end${colors.reset}`)
|
||||
console.log()
|
||||
}
|
||||
|
||||
const nativeFunctions = {
|
||||
echo: (...args: any[]) => {
|
||||
return args.join(' ')
|
||||
},
|
||||
println: (...args: any[]) => {
|
||||
const result = args.join(' ')
|
||||
console.log(result)
|
||||
return result
|
||||
},
|
||||
len: (value: any) => {
|
||||
if (typeof value === 'string') return value.length
|
||||
if (Array.isArray(value)) return value.length
|
||||
if (value && typeof value === 'object') return Object.keys(value).length
|
||||
return 0
|
||||
},
|
||||
type: (value: any) => {
|
||||
if (value === null) return 'null'
|
||||
if (Array.isArray(value)) return 'array'
|
||||
return typeof value
|
||||
},
|
||||
range: (start: number, end?: number) => {
|
||||
if (end === undefined) {
|
||||
end = start
|
||||
start = 0
|
||||
}
|
||||
const result: number[] = []
|
||||
for (let i = start; i < end; i++) {
|
||||
result.push(i)
|
||||
}
|
||||
return result
|
||||
},
|
||||
join: (arr: any[], sep: string = ',') => {
|
||||
return arr.join(sep)
|
||||
},
|
||||
split: (str: string, sep: string = ',') => {
|
||||
return str.split(sep)
|
||||
},
|
||||
upper: (str: string) => str.toUpperCase(),
|
||||
lower: (str: string) => str.toLowerCase(),
|
||||
trim: (str: string) => str.trim(),
|
||||
}
|
||||
|
||||
await repl()
|
||||
|
|
@ -8,7 +8,8 @@
|
|||
],
|
||||
"scripts": {
|
||||
"dev": "bun generate-parser && bun --hot src/server/server.tsx",
|
||||
"generate-parser": "lezer-generator src/parser/shrimp.grammar --typeScript -o src/parser/shrimp.ts"
|
||||
"generate-parser": "lezer-generator src/parser/shrimp.grammar --typeScript -o src/parser/shrimp.ts",
|
||||
"repl": "bun generate-parser && bun bin/repl"
|
||||
},
|
||||
"dependencies": {
|
||||
"reefvm": "workspace:*",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user