use std::cell::RefCell;
use std::path::{Path as FsPath, PathBuf};
use std::rc::Rc;
use rustyline::error::ReadlineError;
use rustyline::DefaultEditor;
use typelisp::project::{find_src_root, load_file_flat, Loader};
use typelisp::*;
const PROMPT_PRIMARY: &str = "typl> ";
const PROMPT_CONTINUE: &str = "... ";
const DEFAULT_HEAP_CELLS: usize = 1 << 16;
const TYPL_VERSION: &str = env!("CARGO_PKG_VERSION");
const HELP: &str = "\
Usage:
typl [OPTIONS] start the REPL
typl [OPTIONS] FILE [ARGS...] run FILE; ARGS are its (command-line-args)
typl [--lib-dir DIR] -c SOURCE [-o OUTPUT] compile SOURCE to an executable
Options:
-c, --compile SOURCE compile SOURCE to a native executable
-o OUTPUT name of the executable (default: SOURCE without .typl)
--lib-dir DIR link DIR/libtypelisp_front.a into compiled executables
(default: a release typl writes the one built into
it to $TYPELISP_HOME/lib, ~/.typelisp/lib if unset;
a debug typl uses the one in the tree it was built in)
--image FILE start from a dump written by (dump ...)
--heap-cells N initial capacity of the cons arena, in cells
--feature NAME add a feature for #+/#- (repeatable)
--on-redefine=POLICY on redefinition: warn (default), error or silent
--remove-lib remove the library this typl wrote for compiled
executables to link, and exit
--remove-lib --others remove the ones other builds of typl wrote instead
--remove-lib --all remove every build's
--help print this help and exit
--version print the version and exit
--help, --version, --remove-lib, --others and --all after FILE are passed to
the program.
--image, --heap-cells and --feature cannot be combined with -c.
";
fn main() -> rustyline::Result<()> {
if let Some(warning) = typelisp::compile::orc::unsupported_host_warning() {
eprintln!("{}", warning);
}
let args: Vec<String> = std::env::args().skip(1).collect();
let (heap_cells, args) = parse_heap_cells(args);
let (features, args) = parse_features(args);
let (image, args) = parse_image(args);
let (lib_dir, args) = parse_lib_dir(args);
let own = &args[..args.iter().position(|a| !a.starts_with("--")).unwrap_or(args.len())];
if own.iter().any(|a| a == "--help") {
print!("{}", HELP);
std::process::exit(0);
}
if own.iter().any(|a| a == "--version") {
println!("typl {}", TYPL_VERSION);
std::process::exit(0);
}
let (others, all) = (own.iter().any(|a| a == "--others"), own.iter().any(|a| a == "--all"));
if own.iter().any(|a| a == "--remove-lib") {
let which = match (others, all) {
(false, false) => typelisp::compile::aot::RemoveLib::This,
(true, false) => typelisp::compile::aot::RemoveLib::Others,
(false, true) => typelisp::compile::aot::RemoveLib::All,
(true, true) => {
eprintln!("--remove-lib: --others and --all cannot be combined");
std::process::exit(1);
}
};
match typelisp::compile::aot::remove_written_archives(which) {
Ok(removed) if removed.is_empty() => println!("no library to remove"),
Ok(removed) => {
for dir in removed {
println!("removed {}", dir.display());
}
}
Err(e) => {
eprintln!("--remove-lib: {}", e);
std::process::exit(1);
}
}
std::process::exit(0);
}
for (given, flag) in [(others, "--others"), (all, "--all")] {
if given {
eprintln!("{}: only means something with --remove-lib", flag);
std::process::exit(1);
}
}
if let Some(dir) = lib_dir {
if let Err(e) = typelisp::compile::aot::set_lib_dir(dir) {
eprintln!("--lib-dir: {}", e);
std::process::exit(1);
}
}
if let Some(flag) = args.first().filter(|a| *a == "-c" || *a == "--compile") {
for (given, given_flag) in [(heap_cells.is_some(), "--heap-cells"), (!features.is_empty(), "--feature"), (image.is_some(), "--image")] {
if given {
eprintln!("{}: {} has no effect on compilation", flag, given_flag);
std::process::exit(1);
}
}
std::process::exit(compile_command(flag, &args[1..]));
}
let heap_cells = heap_cells.unwrap_or(DEFAULT_HEAP_CELLS);
if let Some(pos) = args.iter().position(|a| !a.starts_with("--")) {
typelisp_rt::sys_builtin::set_command_line_args(args[pos..].to_vec());
std::process::exit(run_file(&args[pos], heap_cells, features, image));
}
typelisp_rt::sys_builtin::set_command_line_args(vec!["typl".to_string()]);
repl(heap_cells, features, image)
}
fn compile_command(flag: &str, args: &[String]) -> i32 {
let mut source = None;
let mut output = None;
let mut it = args.iter();
while let Some(a) = it.next() {
if a == "-o" {
match it.next() {
Some(v) => output = Some(PathBuf::from(v)),
None => {
eprintln!("-o: needs a path for the executable");
return 1;
}
}
} else if source.is_none() && !a.starts_with('-') {
source = Some(PathBuf::from(a));
} else {
eprintln!("{}: unexpected argument `{}` (usage: typl {} SOURCE [-o OUTPUT])", flag, a, flag);
return 1;
}
}
let Some(source) = source else {
eprintln!("{}: needs a source file (usage: typl {} SOURCE [-o OUTPUT])", flag, flag);
return 1;
};
let output = match output {
Some(o) => o,
None if source.extension().is_some_and(|e| e == "typl") => source.with_extension(""),
None => {
eprintln!("{}: {} does not end in .typl, so give the executable's name with -o", flag, source.display());
return 1;
}
};
match typelisp::compile::aot::compile_file(&source.to_string_lossy(), &output.to_string_lossy()) {
Ok(()) => 0,
Err(e) => {
eprintln!("error: {}", e);
1
}
}
}
fn take_flag_values(args: Vec<String>, flag: &str, missing: &str) -> (Vec<String>, Vec<String>) {
let mut values = Vec::new();
let mut rest = Vec::with_capacity(args.len());
let mut it = args.into_iter();
while let Some(a) = it.next() {
if a == flag {
match it.next() {
Some(v) => values.push(v),
None => {
eprintln!("{}: {}", flag, missing);
std::process::exit(1);
}
}
} else if let Some(v) = a.strip_prefix(flag).and_then(|v| v.strip_prefix('=')) {
values.push(v.to_string());
} else {
rest.push(a);
}
}
(values, rest)
}
fn parse_lib_dir(args: Vec<String>) -> (Option<PathBuf>, Vec<String>) {
let (dirs, rest) = take_flag_values(args, "--lib-dir", "needs a folder");
(dirs.last().map(PathBuf::from), rest)
}
fn parse_image(args: Vec<String>) -> (Option<PathBuf>, Vec<String>) {
let (images, rest) = take_flag_values(args, "--image", "needs a path to a dump");
(images.last().map(PathBuf::from), rest)
}
fn parse_features(args: Vec<String>) -> (Vec<String>, Vec<String>) {
take_flag_values(args, "--feature", "needs a feature name")
}
fn parse_heap_cells(args: Vec<String>) -> (Option<usize>, Vec<String>) {
let (values, rest) = take_flag_values(args, "--heap-cells", "needs a positive integer (number of cons cells)");
let mut cells = None;
for v in &values {
cells = Some(parse_heap_cells_value(v));
}
(cells, rest)
}
fn parse_heap_cells_value(v: &str) -> usize {
let max = typelisp::Heap::max_capacity();
match v.parse::<usize>() {
Ok(n) if n > max => {
eprintln!(
"--heap-cells: `{}` cells would not fit in this machine's address space ({} is the maximum)",
v, max
);
std::process::exit(1);
}
Ok(n) if n > 0 => n,
_ => {
eprintln!("--heap-cells: `{}` is not a positive integer", v);
std::process::exit(1);
}
}
}
fn load_environment(
heap: &mut Heap,
checker: &mut Checker,
interp: &mut Interp,
image: Option<PathBuf>,
) -> Result<(), String> {
match image {
Some(path) => {
let bytes = std::fs::read(&path).map_err(|e| format!("--image {}: {}", path.display(), e))?;
typelisp::compile::dump::load_image(
heap,
checker,
interp,
std::borrow::Cow::Owned(bytes),
&path.display().to_string(),
)?;
}
None => {
load_prelude(heap, checker, interp);
typelisp::load_compiler_aot(heap, checker, interp);
}
}
interp.start_recording(checker.signature(heap)?);
Ok(())
}
fn run_file(file: &str, heap_cells: usize, features: Vec<String>, image: Option<PathBuf>) -> i32 {
let mut heap = Heap::with_capacity(heap_cells);
let reader = Reader::with_features(features);
let mut checker = Checker::new();
checker.set_redef_policy(parse_redef_policy());
let mut interp = Interp::new();
if let Err(e) = load_environment(&mut heap, &mut checker, &mut interp, image) {
eprintln!("error: {}", e);
return 1;
}
let file = PathBuf::from(file);
let dir = file.parent().filter(|p| !p.as_os_str().is_empty()).map(FsPath::to_path_buf).unwrap_or_else(|| PathBuf::from("."));
let src_root = find_src_root(&dir).unwrap_or(dir);
let mut loader = Loader::new(src_root.clone());
let result = loader.load_entry(&mut heap, &reader, &mut checker, &mut interp, &file);
for w in checker.take_warnings() {
eprintln!("{}", w);
}
if let Err(e) = result {
eprintln!("error: {}", e);
return 1;
}
let entry_ns = typelisp::project::module_segs_for(&file, &src_root).unwrap_or_default();
checker.set_current_ns(entry_ns);
let checker = Rc::new(RefCell::new(checker));
interp.set_checker(Rc::clone(&checker));
for tl in loader.take_pending() {
if let Err(e) = interp.exec(&mut heap, tl) {
eprintln!("error: {}", e);
return 1;
}
}
0
}
fn repl(heap_cells: usize, features: Vec<String>, image: Option<PathBuf>) -> rustyline::Result<()> {
let mut heap = Heap::with_capacity(heap_cells);
let reader = Reader::with_features(features);
let checker = Rc::new(RefCell::new(Checker::new()));
checker.borrow_mut().set_redef_policy(parse_redef_policy());
let mut interp = Interp::new();
if let Err(e) = load_environment(&mut heap, &mut checker.borrow_mut(), &mut interp, image) {
eprintln!("error: {}", e);
std::process::exit(1);
}
interp.set_checker(Rc::clone(&checker));
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let src_root = find_src_root(&cwd).unwrap_or(cwd);
let mut loader = Loader::new(src_root);
let mut rl = DefaultEditor::new()?;
let hist_path = history_path();
let _ = rl.load_history(&hist_path);
let mut pending = String::new();
loop {
let prompt = if pending.is_empty() { PROMPT_PRIMARY } else { PROMPT_CONTINUE };
match heap.native(|| rl.readline(prompt)) {
Ok(line) => {
let trimmed = line.trim();
if pending.is_empty() && (trimmed == ":quit" || trimmed == ":exit") {
break;
}
let _ = rl.add_history_entry(line.as_str());
typelisp_abi::dribble::note(&line);
typelisp_abi::dribble::note("\n");
pending.push_str(&line);
pending.push('\n');
try_run_pending(&mut heap, &reader, &checker, &mut interp, &mut loader, &mut pending);
}
Err(ReadlineError::Interrupted) => {
pending.clear();
println!();
}
Err(ReadlineError::Eof) => break,
Err(e) => {
eprintln!("readline error: {}", e);
break;
}
}
}
let _ = rl.save_history(&hist_path);
Ok(())
}
fn parse_redef_policy() -> RedefPolicy {
for arg in std::env::args().skip(1) {
if let Some(v) = arg.strip_prefix("--on-redefine=") {
return match v {
"warn" => RedefPolicy::Warn,
"error" => RedefPolicy::Error,
"silent" => RedefPolicy::Silent,
other => {
eprintln!("unknown --on-redefine value `{}` (expected warn/error/silent), using warn", other);
RedefPolicy::Warn
}
};
}
}
RedefPolicy::Warn
}
fn history_path() -> PathBuf {
match std::env::var_os("HOME") {
Some(home) if !home.is_empty() => PathBuf::from(home).join(".typl_history"),
_ => PathBuf::from(".typl_history"),
}
}
fn try_run_pending(
heap: &mut Heap,
reader: &Reader,
checker: &RefCell<Checker>,
interp: &mut Interp,
loader: &mut Loader,
pending: &mut String,
) {
let mark = heap.root_count();
let mut forms = reader.forms_in("<stdin>", pending);
let mut consumed = 0usize;
let mut fatal: Option<Error> = None;
let mut incomplete = false;
loop {
let (v, loc) = match forms.next_form_with(heap, Some(&*interp)) {
Ok(Some(pair)) => pair,
Ok(None) => break,
Err(e) => {
if is_incomplete(&e) {
incomplete = true;
} else {
fatal = Some(e);
}
break;
}
};
if let Err(e) = loader.load_uses_in(heap, reader, &mut checker.borrow_mut(), interp, &[v]) {
fatal = Some(e);
break;
}
let mut load_failed = false;
for tl in loader.take_pending() {
if let Err(e) = interp.exec(heap, tl) {
eprintln!("error: {}", e);
load_failed = true;
break;
}
}
if load_failed {
break;
}
let result = checker.borrow_mut().check_form_at(heap, &*interp, v, Some(loc));
for w in checker.borrow_mut().take_warnings() {
eprintln!("{}", w);
}
let tl = match result {
Ok(tl) => tl,
Err(e) => {
fatal = Some(e);
break;
}
};
if let Some(path) = typelisp::project::load_path_of(heap, tl) {
if let Err(e) = load_file_flat(heap, reader, &mut checker.borrow_mut(), interp, FsPath::new("."), &path) {
fatal = Some(e);
break;
}
} else {
match interp.exec(heap, tl) {
Ok(Some(v)) => {
let repr = {
let chk = checker.borrow();
let reg = chk.registry();
chk.expr_type().map(|ty| check::repr::Repr::of_by(ty, &|p| reg.type_def(p).map(|d| d.kind)))
};
heap.push_root(v);
let rendered = interp.render_readably(heap, v, repr.as_ref());
heap.pop_root();
match rendered {
Ok(text) => {
typelisp_abi::dribble::note(&text);
typelisp_abi::dribble::note("\n");
println!("{}", text);
}
Err(e) => {
eprintln!("error: {}", e);
break;
}
}
}
Ok(None) => {}
Err(e) => {
eprintln!("error: {}", e);
break;
}
}
}
consumed = forms.pos();
}
for w in checker.borrow_mut().take_warnings() {
eprintln!("{}", w);
}
while heap.root_count() > mark {
heap.pop_root();
}
if incomplete {
*pending = pending.chars().skip(consumed).collect();
return;
}
pending.clear();
if let Some(e) = fatal {
eprintln!("error: {}", e);
}
}
fn is_incomplete(e: &Error) -> bool {
matches!(
e.kind(),
Error::IllegalEndWhileReadingList | Error::IllegalEndOfString | Error::IllegalEndOfEscapeSequence
)
}