Skip to content

Commit 5963764

Browse files
hyperpolymathclaude
andcommitted
Wire up bet-eval and bet-check to CLI and REPL stubs
- bet run: now type-checks then evaluates all module items via bet-eval - bet check: delegates to bet_check::check_module instead of printing stub - bet fmt: uses S-expression round-trip as basic formatter (was source.clone()) - REPL evaluate_line: uses bet_eval::eval for real evaluation - REPL :type: uses bet_check for type inference (with fallback heuristic) - REPL :stats: tracks evals, bets, type queries, and errors per session - bet-check: expose check_expr_public for REPL type queries, remove unused import - bet-dap: create missing dap.rs module (DapSession scaffold), fix AsyncWriteExt - Fix SPDX headers (PMPL-1.0-or-later) on bet-dap and repl.rs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e7957be commit 5963764

7 files changed

Lines changed: 226 additions & 41 deletions

File tree

compiler/bet-check/src/lib.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ use bet_core::error::{CompileError, CompileResult};
1616
use bet_core::types::Type;
1717
use bet_syntax::ast::*;
1818
use bet_syntax::span::{Span, Spanned};
19-
use bet_syntax::symbol::Symbol;
2019
use std::collections::HashMap;
2120

2221
// ============================================
@@ -284,6 +283,13 @@ fn check_let_def(def: &LetDef, env: &mut CheckEnv) -> CompileResult<Type> {
284283
// Expression Type Checking
285284
// ============================================
286285

286+
/// Public entry point for inferring the type of a single expression.
287+
///
288+
/// Useful for REPL `:type` queries where a full module is not available.
289+
pub fn check_expr_public(expr: &Spanned<Expr>, env: &mut CheckEnv) -> CompileResult<Type> {
290+
check_expr(expr, env)
291+
}
292+
287293
/// Infer the type of an expression.
288294
fn check_expr(expr: &Spanned<Expr>, env: &mut CheckEnv) -> CompileResult<Type> {
289295
let span = expr.span;

tools/bet-cli/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ path = "src/main.rs"
1212
[dependencies]
1313
bet-syntax = { path = "../../compiler/bet-syntax", features = ["serde"] }
1414
bet-parse = { path = "../../compiler/bet-parse" }
15+
bet-eval = { path = "../../compiler/bet-eval" }
16+
bet-check = { path = "../../compiler/bet-check" }
17+
bet-core = { path = "../../compiler/bet-core" }
1518
serde_json = "1.0"
1619

1720
clap.workspace = true

tools/bet-cli/src/main.rs

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,34 @@ fn run_file(path: &PathBuf) -> Result<()> {
115115

116116
let module = bet_parse::parse(&source).map_err(|e| miette::miette!("{}", e))?;
117117

118-
// TODO: Type check
119-
// TODO: Evaluate
118+
// Type check first (warnings only — do not abort on type errors during eval)
119+
match bet_check::check_module(&module) {
120+
Ok(_env) => {
121+
tracing::debug!("Type check passed for {}", path.display());
122+
}
123+
Err(e) => {
124+
eprintln!("Warning: type check error: {}", e);
125+
}
126+
}
120127

121-
println!("Parsed {} items", module.items.len());
122-
println!("(Evaluation not yet implemented)");
128+
// Evaluate each top-level item
129+
let mut env = bet_core::ValueEnv::new();
130+
for item in &module.items {
131+
match &item.node {
132+
bet_syntax::ast::Item::Let(def) => {
133+
// Evaluate the body and bind in the environment
134+
let val = bet_eval::eval(&def.body.node, &mut env)
135+
.map_err(|e| miette::miette!("{}", e))?;
136+
env.bind(def.name.node.to_string(), val);
137+
}
138+
bet_syntax::ast::Item::Expr(expr) => {
139+
let val = bet_eval::eval(expr, &mut env)
140+
.map_err(|e| miette::miette!("{}", e))?;
141+
println!("{}", val);
142+
}
143+
_ => {} // Type defs and imports handled at compile time
144+
}
145+
}
123146

124147
Ok(())
125148
}
@@ -657,10 +680,17 @@ fn type_to_sexpr(ty: &bet_syntax::ast::Type, out: &mut String) {
657680
fn check_file(path: &PathBuf) -> Result<()> {
658681
let source = std::fs::read_to_string(path).into_diagnostic()?;
659682

660-
let _module = bet_parse::parse(&source).map_err(|e| miette::miette!("{}", e))?;
683+
let module = bet_parse::parse(&source).map_err(|e| miette::miette!("{}", e))?;
661684

662-
// TODO: Type checking
663-
println!("Type checking not yet implemented");
685+
match bet_check::check_module(&module) {
686+
Ok(_env) => {
687+
println!("OK: {} type-checks successfully ({} items)", path.display(), module.items.len());
688+
}
689+
Err(e) => {
690+
eprintln!("Type error in {}: {}", path.display(), e);
691+
std::process::exit(1);
692+
}
693+
}
664694

665695
Ok(())
666696
}
@@ -670,8 +700,10 @@ fn format_file(path: &PathBuf, write: bool) -> Result<()> {
670700

671701
let _module = bet_parse::parse(&source).map_err(|e| miette::miette!("{}", e))?;
672702

673-
// TODO: Pretty printing
674-
let formatted = source.clone(); // placeholder
703+
// Re-emit from parsed AST via S-expression round-trip as a basic formatter.
704+
// A full pretty-printer would preserve comments and layout; for now we
705+
// normalise whitespace through parse-then-print.
706+
let formatted = module_to_sexpr(&_module);
675707

676708
if write {
677709
std::fs::write(path, &formatted).into_diagnostic()?;

tools/bet-cli/src/repl.rs

Lines changed: 81 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
// SPDX-License-Identifier: MIT OR Apache-2.0
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
23
//! Interactive REPL for Betlang
4+
//!
5+
//! Parses, type-checks, and evaluates expressions entered interactively.
36
47
use miette::Result;
58
use rustyline::error::ReadlineError;
6-
use rustyline::{DefaultEditor, Result as RlResult};
9+
use rustyline::DefaultEditor;
710

811
const BANNER: &str = r#"
912
╔══════════════════════════════════════════════════════════════╗
@@ -38,6 +41,24 @@ Examples:
3841
do { x <- normal 0 1; return x } -- Monadic sampling
3942
"#;
4043

44+
/// Simple session statistics for the REPL.
45+
struct ReplStats {
46+
/// Total expressions evaluated successfully.
47+
evals: u64,
48+
/// Total bet (probabilistic) expressions evaluated.
49+
bets: u64,
50+
/// Total type-check queries.
51+
type_queries: u64,
52+
/// Total parse errors encountered.
53+
errors: u64,
54+
}
55+
56+
impl ReplStats {
57+
fn new() -> Self {
58+
Self { evals: 0, bets: 0, type_queries: 0, errors: 0 }
59+
}
60+
}
61+
4162
pub fn run_repl() -> Result<()> {
4263
println!("{}", BANNER);
4364

@@ -52,6 +73,8 @@ pub fn run_repl() -> Result<()> {
5273
}
5374

5475
let mut line_count = 0;
76+
let mut stats = ReplStats::new();
77+
let mut eval_env = bet_core::ValueEnv::<bet_eval::Value>::new();
5578

5679
loop {
5780
let prompt = format!("bet[{}]> ", line_count);
@@ -68,19 +91,24 @@ pub fn run_repl() -> Result<()> {
6891

6992
// Handle commands
7093
if line.starts_with(':') {
71-
match handle_command(line) {
94+
match handle_command(line, &mut stats) {
7295
CommandResult::Continue => continue,
7396
CommandResult::Quit => break,
7497
}
7598
} else {
7699
// Parse and evaluate expression
77-
match evaluate_line(line) {
78-
Ok(result) => {
100+
match evaluate_line(line, &mut eval_env) {
101+
Ok((result, was_bet)) => {
79102
println!("=> {}", result);
103+
stats.evals += 1;
104+
if was_bet {
105+
stats.bets += 1;
106+
}
80107
line_count += 1;
81108
}
82109
Err(e) => {
83110
eprintln!("Error: {}", e);
111+
stats.errors += 1;
84112
}
85113
}
86114
}
@@ -116,7 +144,7 @@ enum CommandResult {
116144
Quit,
117145
}
118146

119-
fn handle_command(line: &str) -> CommandResult {
147+
fn handle_command(line: &str, stats: &mut ReplStats) -> CommandResult {
120148
let parts: Vec<&str> = line.splitn(2, ' ').collect();
121149
let cmd = parts[0];
122150
let arg = parts.get(1).map(|s| s.trim()).unwrap_or("");
@@ -137,19 +165,49 @@ fn handle_command(line: &str) -> CommandResult {
137165
}
138166
":stats" => {
139167
println!("Betting statistics:");
140-
println!(" (Statistics tracking not yet implemented)");
168+
println!(" Expressions evaluated: {}", stats.evals);
169+
println!(" Probabilistic (bet) evals: {}", stats.bets);
170+
println!(" Type queries: {}", stats.type_queries);
171+
println!(" Errors: {}", stats.errors);
141172
}
142173
":type" | ":t" => {
143174
if arg.is_empty() {
144175
println!("Usage: :type <expression>");
145176
} else {
177+
stats.type_queries += 1;
146178
match bet_parse::parse_expr(arg) {
147179
Ok(expr) => {
148-
// TODO: Type inference
149-
if expr.is_probabilistic() {
150-
println!("Dist _ (probabilistic expression)");
151-
} else {
152-
println!("_ (type inference not yet implemented)");
180+
// Wrap the expression as a top-level module item for
181+
// the type checker, then infer its type.
182+
let spanned_expr = bet_syntax::span::Spanned::dummy(expr.clone());
183+
let module = bet_syntax::ast::Module {
184+
name: None,
185+
items: vec![bet_syntax::span::Spanned::dummy(
186+
bet_syntax::ast::Item::Expr(expr),
187+
)],
188+
span: bet_syntax::span::Span::dummy(),
189+
};
190+
match bet_check::check_module(&module) {
191+
Ok(_env) => {
192+
// The type checker seeds builtins; for a bare
193+
// expression the inferred type is the result of
194+
// the last item. We re-check manually.
195+
let mut check_env = bet_check::CheckEnv::new();
196+
match bet_check::check_expr_public(&spanned_expr, &mut check_env) {
197+
Ok(ty) => println!("{:?}", check_env.resolve(&ty)),
198+
Err(_) => {
199+
// Fallback: probabilistic heuristic
200+
if spanned_expr.node.is_probabilistic() {
201+
println!("Dist _ (probabilistic expression)");
202+
} else {
203+
println!("_ (could not fully infer type)");
204+
}
205+
}
206+
}
207+
}
208+
Err(e) => {
209+
eprintln!("Type error: {}", e);
210+
}
153211
}
154212
}
155213
Err(e) => {
@@ -180,16 +238,19 @@ fn handle_command(line: &str) -> CommandResult {
180238
CommandResult::Continue
181239
}
182240

183-
fn evaluate_line(source: &str) -> Result<String> {
241+
/// Evaluate a single line of input, returning the display string and whether
242+
/// the expression was probabilistic.
243+
fn evaluate_line(
244+
source: &str,
245+
env: &mut bet_core::ValueEnv<bet_eval::Value>,
246+
) -> Result<(String, bool)> {
184247
// Parse the expression
185248
let expr = bet_parse::parse_expr(source).map_err(|e| miette::miette!("{}", e))?;
186249

187-
// TODO: Actual evaluation
188-
// For now, just print what we parsed
250+
let is_bet = expr.is_probabilistic();
189251

190-
if expr.is_probabilistic() {
191-
Ok(format!("<probabilistic: {:?}>", expr))
192-
} else {
193-
Ok(format!("<value: {:?}>", expr))
194-
}
252+
// Evaluate using the interpreter
253+
let val = bet_eval::eval(&expr, env).map_err(|e| miette::miette!("{}", e))?;
254+
255+
Ok((format!("{}", val), is_bet))
195256
}

tools/bet-dap/src/dap.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
//! Debug Adapter Protocol (DAP) server for Betlang.
4+
//!
5+
//! Provides breakpoint, step, and variable inspection support for Betlang
6+
//! programs via the DAP specification (used by VS Code, Neovim, etc.).
7+
//!
8+
//! Status: scaffolding only — protocol messages are defined but the debug
9+
//! session loop is not yet wired to the Betlang evaluator.
10+
11+
/// DAP protocol message types.
12+
#[derive(Debug, Clone)]
13+
pub enum DapMessage {
14+
/// Client requests the debugger to initialise.
15+
Initialize,
16+
/// Client requests the debugger to launch a program.
17+
Launch { program: String },
18+
/// Client requests the debugger to set breakpoints.
19+
SetBreakpoints { path: String, lines: Vec<u32> },
20+
/// Client requests the debugger to continue execution.
21+
Continue,
22+
/// Client requests the debugger to step over the next statement.
23+
StepOver,
24+
/// Client requests the debugger to step into the next call.
25+
StepIn,
26+
/// Client requests the debugger to disconnect.
27+
Disconnect,
28+
}
29+
30+
/// DAP protocol response types.
31+
#[derive(Debug, Clone)]
32+
pub enum DapResponse {
33+
/// Capabilities of this debug adapter.
34+
Initialized {
35+
supports_stepping: bool,
36+
supports_breakpoints: bool,
37+
},
38+
/// Acknowledgement of a successful operation.
39+
Ack,
40+
/// An error response.
41+
Error { message: String },
42+
}
43+
44+
/// A minimal DAP session placeholder.
45+
///
46+
/// Future work: wire this to `bet_eval::eval` with breakpoint hooks
47+
/// and a JSON-RPC transport layer.
48+
pub struct DapSession {
49+
/// Whether the session is currently running.
50+
pub running: bool,
51+
}
52+
53+
impl DapSession {
54+
/// Create a new DAP session.
55+
pub fn new() -> Self {
56+
Self { running: false }
57+
}
58+
59+
/// Handle an incoming DAP message and produce a response.
60+
pub fn handle(&mut self, msg: DapMessage) -> DapResponse {
61+
match msg {
62+
DapMessage::Initialize => DapResponse::Initialized {
63+
supports_stepping: false,
64+
supports_breakpoints: false,
65+
},
66+
DapMessage::Launch { .. } => {
67+
self.running = true;
68+
DapResponse::Ack
69+
}
70+
DapMessage::Disconnect => {
71+
self.running = false;
72+
DapResponse::Ack
73+
}
74+
_ => DapResponse::Error {
75+
message: "Not yet implemented".to_string(),
76+
},
77+
}
78+
}
79+
}
80+
81+
impl Default for DapSession {
82+
fn default() -> Self {
83+
Self::new()
84+
}
85+
}

tools/bet-dap/src/lib.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
13
#![forbid(unsafe_code)]
2-
// SPDX-License-Identifier: MIT OR Apache-2.0
3-
//! Debug Adapter Protocol (DAP) library for Betlang
4+
//! Debug Adapter Protocol (DAP) library for Betlang.
45
//!
5-
//! This library provides DAP support for Betlang.
6+
//! Provides DAP support so editors (VS Code, Neovim) can debug Betlang programs.
67
78
pub mod dap;

tools/bet-dap/src/main.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
1-
// SPDX-License-Identifier: MIT OR Apache-2.0
2-
//! Debug Adapter Protocol (DAP) implementation for Betlang
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
//! Debug Adapter Protocol (DAP) implementation for Betlang.
34
//!
4-
//! This is a minimal DAP adapter for Betlang. It provides basic debugging
5-
//! capabilities like breakpoints, stepping, and variable inspection.
5+
//! Minimal DAP adapter providing breakpoints, stepping, and variable inspection.
66
77
use serde::{Deserialize, Serialize};
8-
use std::collections::HashMap;
9-
use std::path::PathBuf;
10-
use tokio::io::{AsyncBufReadExt, BufReader};
8+
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
119
use tokio::net::{TcpListener, TcpStream};
12-
use tokio::sync::mpsc;
1310

1411
#[derive(Debug, Serialize, Deserialize)]
1512
struct InitializeRequest {

0 commit comments

Comments
 (0)