mirror of
https://github.com/bearlanguageorg/bear.git
synced 2026-08-26 16:47:01 +00:00
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>
204 lines
5.2 KiB
V
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
|
|
}
|