This commit is contained in:
parent
c75dafac8e
commit
2c50b04108
9 changed files with 100 additions and 17 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -93,6 +93,7 @@ false
|
|||
true
|
||||
>> 1 + 3i < 5
|
||||
Error: type_error: cannot compare 1.0+3.0i and 5
|
||||
in <repl> at 4
|
||||
```
|
||||
|
||||
The boolean operators `and`, `or`, and `not` perform the expected operations
|
||||
|
|
|
@ -11,6 +11,7 @@ message (which must be a string).
|
|||
```talc
|
||||
>> throw(:my_exception, "something bad happened!")
|
||||
Error: my_exception: something bad happened!
|
||||
in <repl> at 1
|
||||
```
|
||||
|
||||
The exception can then be caught in a try-catch block:
|
||||
|
|
|
@ -57,6 +57,7 @@ you to write multiline programs.
|
|||
45
|
||||
>> y
|
||||
Error: name_error: undefined global y
|
||||
in <repl> at 4
|
||||
```
|
||||
|
||||
Later we will see other expressions that create scopes, so it is important to
|
||||
|
|
|
@ -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<Option<String>, 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,
|
||||
|
|
|
@ -6,6 +6,32 @@ use super::{ParserError, Pos, Span};
|
|||
|
||||
type Result<T> = std::result::Result<T, ParserError>;
|
||||
|
||||
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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -438,15 +438,7 @@ fn format_arg(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[native_func(2, "fmt")]
|
||||
pub fn fmt_(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
||||
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<LString> {
|
||||
let mut res = LString::new();
|
||||
let mut faidx = 0;
|
||||
let mut i = 0;
|
||||
|
@ -493,8 +485,21 @@ pub fn fmt_(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
|
|||
}
|
||||
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<Value>) -> Result<Value> {
|
||||
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())
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue