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
47use miette:: Result ;
58use rustyline:: error:: ReadlineError ;
6- use rustyline:: { DefaultEditor , Result as RlResult } ;
9+ use rustyline:: DefaultEditor ;
710
811const 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+
4162pub 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}
0 commit comments