Files
bear/repl.v
T
allexanderbergmns 9b22be48a5 Production pass: floats, GC, type checking, and tooling
Adds floats, bitwise ops, UTF-8 strings with methods, try/catch,
closures, generics validation, a compile-time type checker, a
mark-and-sweep GC, source-level debug info, constant folding, and
the repl/fmt/package-manager commands.

🤖 Generated with Codebuff
Co-Authored-By: Codebuff <noreply@codebuff.com>
2026-08-25 14:29:15 +02:00

204 lines
5.2 KiB
V

// repl.v — an interactive read-eval-print loop for VuurRaaf.
//
// The REPL keeps two growing buffers: top-level declarations (fn / struct /
// enum / const / import / interface) and persistent statements (let, assert,
// assignment). Every other line — expressions, println, if/while/for blocks,
// match, try — is transient: it is evaluated once against the accumulated
// state and its output is not repeated on later lines. Expressions are
// echoed like Python's REPL (1 + 1 prints 2); statement keywords run for
// their side effects.
//
// > let x = 5
// > x * 2
// 10
// > fn square(n) { return n * n }
// > square(9)
// 81
//
// Multi-line input is gathered while braces are unbalanced:
//
// > fn fib(n) {
// ... if n < 2 { return n }
// ... return fib(n - 1) + fib(n - 2)
// ... }
module main
import os
import obj
import compiler
import linker
import vm
fn toolchain_repl() ! {
println('VuurRaaf ${version} — type :help for commands, :quit to exit')
mut top := '' // fn / struct / enum / const / import declarations
mut body := '' // persistent statements (let / assert / assignment)
mut pending := '' // accumulated multi-line input
mut depth := 0 // brace balance of pending input
for {
prompt := if depth > 0 { '... ' } else { '> ' }
line := os.input(prompt)
if line == '' {
continue
}
trimmed := line.trim_space()
if trimmed.starts_with(':') && depth == 0 {
act := repl_meta(trimmed)
if act == 1 {
break
}
if act == 2 {
top = ''
body = ''
println('session reset')
}
continue
}
// gather multi-line input until braces balance
pending += line + '\n'
depth += brace_delta(line)
if depth > 0 {
continue
}
block := pending
pending = ''
depth = 0
match repl_classify(block) {
'top' {
// fn / struct / enum / const / import — compile-time only
candidate := top + '\n' + block
if repl_compile(candidate, body, '') {
top = candidate
}
}
'body' {
candidate := body + '\n' + block
if repl_compile(top, candidate, '') {
body = candidate
}
}
else {
// transient: run once against the accumulated state
tail := if repl_is_expr(block) { 'println(${block.trim_space()})' } else { block }
repl_compile(top, body, tail)
}
}
}
}
// repl_meta handles :quit / :reset / :help. Returns 1 to quit, 2 to reset,
// and 0 otherwise.
fn repl_meta(cmd string) int {
match cmd {
':q', ':quit', ':exit' {
println('bye')
return 1
}
':reset' {
return 2
}
':help', ':h' {
println(' :help this help')
println(' :quit :exit leave the REPL')
println(' :reset clear all definitions')
println(' expressions are echoed; let/assign/fn persist')
}
else {
eprintln('repl: unknown command "${cmd}" (:help for a list)')
}
}
return 0
}
// repl_compile compiles top declarations plus a main body (with an optional
// trailing tail statement), links, runs, and reports errors without exiting.
// Returns true when the program compiled and ran cleanly.
fn repl_compile(top string, body string, tail string) bool {
src := top + '\nfn main() {\n' + body + '\n' + tail + '\n}'
o := compiler.compile(src) or {
eprintln('repl: ${err.msg()}')
return false
}
tmp_obj := os.join_path(os.temp_dir(), 'vr_repl_${os.getpid()}.vobj')
tmp_bin := os.join_path(os.temp_dir(), 'vr_repl_${os.getpid()}.vbin')
defer {
os.rm(tmp_obj) or {}
os.rm(tmp_bin) or {}
}
obj.write(tmp_obj, o) or {
eprintln('repl: ${err.msg()}')
return false
}
linker.link([tmp_obj], tmp_bin) or {
eprintln('repl: ${err.msg()}')
return false
}
bin := obj.read_bin(tmp_bin) or {
eprintln('repl: ${err.msg()}')
return false
}
vm.run(bin, 'main', false) or {
eprintln('repl: ${err.msg()}')
return false
}
return true
}
// repl_classify buckets a (possibly multi-line) input block.
fn repl_classify(block string) string {
toks := compiler.tokenize(block) or { return 'transient' }
if toks.len == 0 {
return 'transient'
}
match toks[0].kind {
.kw_fn, .kw_struct, .kw_enum, .kw_const, .kw_import, .kw_interface {
return 'top'
}
.kw_let, .kw_assert {
return 'body'
}
.ident {
// assignment persists; anything else is an expression
if toks.len >= 2 && toks[1].kind in [.assign, .plus_eq, .minus_eq, .star_eq, .slash_eq] {
return 'body'
}
return 'transient'
}
else {
return 'transient'
}
}
}
// repl_is_expr reports whether a transient block is a bare expression that
// should be echoed (as opposed to a statement keyword like println or if).
fn repl_is_expr(block string) bool {
toks := compiler.tokenize(block) or { return false }
if toks.len == 0 {
return false
}
match toks[0].kind {
.kw_print, .kw_println, .kw_if, .kw_while, .kw_for, .kw_match, .kw_break, .kw_continue, .kw_return, .kw_try, .kw_throw {
return false
}
else {
return true
}
}
}
// brace_delta counts the brace balance of a line using the tokenizer, so
// braces inside string literals do not confuse continuation detection.
fn brace_delta(line string) int {
toks := compiler.tokenize(line) or { return 0 }
mut d := 0
for t in toks {
if t.kind == .lbrace {
d++
} else if t.kind == .rbrace {
d--
}
}
return d
}