diff --git a/Cargo.lock b/Cargo.lock index a72479b..2dcd065 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -445,9 +445,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "syn" -version = "2.0.93" +version = "2.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" +checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3" dependencies = [ "proc-macro2", "quote", diff --git a/docs/src/install/repl.md b/docs/src/install/repl.md index 2abbd98..b445214 100644 --- a/docs/src/install/repl.md +++ b/docs/src/install/repl.md @@ -27,6 +27,17 @@ Use the up and down arrows to cycle through previous entries. By specifiying a history file with the `-H`/`--histfile` argument, history can be preserved between sessions. +## Output formatting + +The `_fmt` global variable can be used to control output formatting. `_fmt` is +a table where each key is a type and the corresponding value is the format +string used to display that type. For example, setting `_fmt.int = "0x{:X}"` +will cause the REPL to print integers in hexadecimal with a `0x` prefix. + +Values may also be `false` to disable printing entirely. Any other value will +print using the default format. Initially, the value of `_fmt` is +`{ :nil=false }`. + ## Advanced features Use the `-a`/`--show-ast` flag to print the AST generated from the expression. @@ -34,3 +45,4 @@ Use the `-d`/`--disasm` flag to show the disassembled bytecode generated by the compiler. The `--no-opt` flag disables the optimization step, which may affect the bytecode but should not affect behavior. + diff --git a/docs/src/lang/arithmetic.md b/docs/src/lang/arithmetic.md index 58e7058..7c7fce1 100644 --- a/docs/src/lang/arithmetic.md +++ b/docs/src/lang/arithmetic.md @@ -93,6 +93,7 @@ false true >> 1 + 3i < 5 Error: type_error: cannot compare 1.0+3.0i and 5 + in at 4 ``` The boolean operators `and`, `or`, and `not` perform the expected operations diff --git a/docs/src/lang/exceptions.md b/docs/src/lang/exceptions.md index 1652237..d762f77 100644 --- a/docs/src/lang/exceptions.md +++ b/docs/src/lang/exceptions.md @@ -11,6 +11,7 @@ message (which must be a string). ```talc >> throw(:my_exception, "something bad happened!") Error: my_exception: something bad happened! + in at 1 ``` The exception can then be caught in a try-catch block: diff --git a/docs/src/lang/variables.md b/docs/src/lang/variables.md index fe16758..368df1c 100644 --- a/docs/src/lang/variables.md +++ b/docs/src/lang/variables.md @@ -57,6 +57,7 @@ you to write multiline programs. 45 >> y Error: name_error: undefined global y + in at 4 ``` Later we will see other expressions that create scopes, so it is important to diff --git a/talc-bin/src/repl.rs b/talc-bin/src/repl.rs index 0a448e7..4235027 100644 --- a/talc-bin/src/repl.rs +++ b/talc-bin/src/repl.rs @@ -1,4 +1,9 @@ -use std::{cell::RefCell, io::IsTerminal, process::ExitCode, rc::Rc}; +use std::{ + cell::RefCell, + io::{stdout, IsTerminal, Write}, + process::ExitCode, + rc::Rc, +}; use clap::ColorChoice; use rustyline::{ @@ -22,6 +27,7 @@ lazy_static::lazy_static! { pub static ref SYM_PREV1: Symbol = symbol!("_"); pub static ref SYM_PREV2: Symbol = symbol!("__"); pub static ref SYM_PREV3: Symbol = symbol!("___"); + pub static ref SYM__FMT: Symbol = symbol!("_fmt"); } #[derive(Clone, Copy, Default)] @@ -98,6 +104,32 @@ fn read_init_file(args: &Args) -> Result, std::io::Error> { std::fs::read_to_string(file.to_os_str()).map(Some) } +fn print_result(vm: &Vm, result: Value, c: &ReplColors) { + let ty = result.get_type(); + let Some(Value::Table(g_fmt)) = vm.get_global(*SYM__FMT) else { + println!("{result:#}"); + return + }; + let g_fmt_ref = g_fmt.borrow(); + let fmt = g_fmt_ref.get(&ty.into()).unwrap_or(&Value::Nil); + match fmt { + Value::String(fstr) => { + let res = talc_std::format::fmt_inner(fstr, &[result]); + match res { + Ok(s) => { + stdout().write_all(s.as_bytes()).expect("output failed"); + stdout().write_all(b"\n").expect("output failed"); + } + Err(e) => { + eprintln!("{}Error(fmt):{} {e}", c.error, c.reset); + } + } + } + Value::Bool(false) => (), + _ => println!("{result:#}"), + } +} + fn exec_line( vm: &mut Vm, line: &str, @@ -144,9 +176,7 @@ fn exec_line( let prev1 = vm.get_global(*SYM_PREV1).unwrap().clone(); vm.set_global(*SYM_PREV2, prev1); vm.set_global(*SYM_PREV1, v.clone()); - if v != Value::Nil { - println!("{v:#}"); - } + print_result(vm, v, c); } Err(e) => eprintln!("{}Error:{} {e}", c.error, c.reset), } @@ -178,6 +208,12 @@ pub fn repl(args: &Args) -> ExitCode { vm.set_global(*SYM_PREV1, Value::Nil); vm.set_global(*SYM_PREV2, Value::Nil); vm.set_global(*SYM_PREV3, Value::Nil); + vm.set_global( + *SYM__FMT, + Value::new_table(|table| { + table.insert(symbol!(nil).into(), Value::Bool(false)); + }), + ); let init_src = match read_init_file(args) { Ok(s) => s, diff --git a/talc-lang/src/parser/lexer.rs b/talc-lang/src/parser/lexer.rs index 571586c..a7a791e 100644 --- a/talc-lang/src/parser/lexer.rs +++ b/talc-lang/src/parser/lexer.rs @@ -6,6 +6,32 @@ use super::{ParserError, Pos, Span}; type Result = std::result::Result; +pub static KEYWORDS: &[&[u8]] = &[ + b"and", + b"break", + b"catch", + b"continue", + b"do", + b"elif", + b"else", + b"end", + b"false", + b"fn", + b"for", + b"global", + b"if", + b"in", + b"nil", + b"not", + b"or", + b"return", + b"then", + b"true", + b"try", + b"var", + b"while", +]; + #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum TokenKind { Eof, diff --git a/talc-lang/src/value/mod.rs b/talc-lang/src/value/mod.rs index 3849040..fc3f1c8 100644 --- a/talc-lang/src/value/mod.rs +++ b/talc-lang/src/value/mod.rs @@ -8,6 +8,7 @@ use num::{complex::Complex64, BigInt}; use crate::exception::{throw, Exception}; use crate::lstring::{LStr, LString}; use crate::number::{Int, Range, Ratio}; +use crate::parser::KEYWORDS; use crate::prelude::*; use crate::symbol::{Symbol, SYM_HASH_ERROR}; @@ -254,7 +255,7 @@ impl Value { } Self::Symbol(s) => { let name = s.name(); - if name.is_identifier() { + if name.is_identifier() && !KEYWORDS.contains(&name.as_bytes()) { w.push_lstr(name); Ok(()) } else { diff --git a/talc-std/src/format.rs b/talc-std/src/format.rs index f91c1d3..077adfc 100644 --- a/talc-std/src/format.rs +++ b/talc-std/src/format.rs @@ -438,15 +438,7 @@ fn format_arg( Ok(()) } -#[native_func(2, "fmt")] -pub fn fmt_(_: &mut Vm, args: Vec) -> Result { - let [_, fstr, fargs] = unpack_args!(args); - let (Value::String(fstr), Value::List(fargs)) = (&fstr, &fargs) else { - throw!( - *SYM_TYPE_ERROR, - "format expected string and list, got {fstr:#} and {fargs:#}" - ) - }; +pub fn fmt_inner(fstr: &LStr, fargs: &[Value]) -> Result { let mut res = LString::new(); let mut faidx = 0; let mut i = 0; @@ -493,8 +485,21 @@ pub fn fmt_(_: &mut Vm, args: Vec) -> Result { } let code = &fstr[start..i]; i += 1; - format_arg(&fargs.borrow(), &mut faidx, code, &mut res)?; + format_arg(fargs, &mut faidx, code, &mut res)?; } + Ok(res) +} + +#[native_func(2, "fmt")] +pub fn fmt_(_: &mut Vm, args: Vec) -> Result { + let [_, fstr, fargs] = unpack_args!(args); + let (Value::String(fstr), Value::List(fargs)) = (&fstr, &fargs) else { + throw!( + *SYM_TYPE_ERROR, + "format expected string and list, got {fstr:#} and {fargs:#}" + ) + }; + let res = fmt_inner(fstr, &fargs.borrow())?; Ok(res.into()) }