#![feature(trace_macros)] #![feature(custom_attribute)] //#![feature(unrestricted_attribute_tokens)] #![feature(slice_patterns, box_patterns, box_syntax)] //! `schala-lang` is where the Schala programming language is actually implemented. //! It defines the `Schala` type, which contains the state for a Schala REPL, and implements //! `ProgrammingLanguageInterface` and the chain of compiler passes for it. extern crate itertools; #[macro_use] extern crate lazy_static; #[macro_use] extern crate maplit; extern crate schala_repl; #[macro_use] extern crate schala_lang_codegen; extern crate ena; use std::cell::RefCell; use std::rc::Rc; use itertools::Itertools; use schala_repl::{ProgrammingLanguageInterface, ComputationRequest, ComputationResponse, LangMetaRequest, LangMetaResponse, GlobalOutputStats}; macro_rules! bx { ($e:expr) => { Box::new($e) } } #[macro_use] mod util; #[macro_use] mod typechecking; mod tokenizing; mod ast; mod parsing; mod symbol_table; mod builtin; mod reduced_ast; mod eval; /// All bits of state necessary to parse and execute a Schala program are stored in this struct. /// `state` represents the execution state for the AST-walking interpreter, the other fields /// should be self-explanatory. pub struct Schala { source_reference: SourceReference, state: eval::State<'static>, symbol_table: Rc>, type_context: typechecking::TypeContext<'static>, active_parser: Option, } impl Schala { fn get_doc(&self, commands: &Vec<&str>) -> Option { Some(format!("Documentation on commands: {:?}", commands)) } fn handle_custom_interpreter_directives(&mut self, commands: &Vec<&str>) -> Option { Some(format!("Schala-lang command: {:?} not supported", commands.get(0))) } } impl Schala { /// Creates a new Schala environment *without* any prelude. fn new_blank_env() -> Schala { let symbols = Rc::new(RefCell::new(symbol_table::SymbolTable::new())); Schala { source_reference: SourceReference::new(), symbol_table: symbols.clone(), state: eval::State::new(symbols), type_context: typechecking::TypeContext::new(), active_parser: None, } } /// Creates a new Schala environment with the standard prelude, which is defined as ordinary /// Schala code in the file `prelude.schala` pub fn new() -> Schala { let prelude = include_str!("prelude.schala"); let mut s = Schala::new_blank_env(); let request = ComputationRequest { source: prelude, debug_requests: vec![] }; s.run_computation(request); s } } fn tokenizing(input: &str, _handle: &mut Schala, comp: Option<&mut PassDebugArtifact>) -> Result, String> { let tokens = tokenizing::tokenize(input); comp.map(|comp| { let token_string = tokens.iter().map(|t| t.to_string_with_metadata()).join(", "); comp.add_artifact(token_string); }); let errors: Vec = tokens.iter().filter_map(|t| t.get_error()).collect(); if errors.len() == 0 { Ok(tokens) } else { Err(format!("{:?}", errors)) } } fn parsing(input: Vec, handle: &mut Schala, comp: Option<&mut PassDebugArtifact>) -> Result { use crate::parsing::Parser; let mut parser = match handle.active_parser.take() { None => Parser::new(input), Some(parser) => parser }; let ast = parser.parse(); let trace = parser.format_parse_trace(); comp.map(|comp| { /* //TODO need to control which of these debug stages get added let opt = comp.cur_debug_options.get(0).map(|s| s.clone()); match opt { None => comp.add_artifact(TraceArtifact::new("ast", format!("{:?}", ast))), Some(ref s) if s == "compact" => comp.add_artifact(TraceArtifact::new("ast", format!("{:?}", ast))), Some(ref s) if s == "expanded" => comp.add_artifact(TraceArtifact::new("ast", format!("{:#?}", ast))), Some(ref s) if s == "trace" => comp.add_artifact(TraceArtifact::new_parse_trace(trace)), Some(ref x) => println!("Bad parsing debug option: {}", x), }; */ }); ast.map_err(|err| format_parse_error(err, handle)) } fn format_parse_error(error: parsing::ParseError, handle: &mut Schala) -> String { let line_num = error.token.line_num; let ch = error.token.char_num; let line_from_program = handle.source_reference.get_line(line_num); let location_pointer = format!("{}^", " ".repeat(ch)); let line_num_digits = format!("{}", line_num).chars().count(); let space_padding = " ".repeat(line_num_digits); format!(r#" {error_msg} {space_padding} | {line_num} | {} {space_padding} | {} "#, line_from_program, location_pointer, error_msg=error.msg, space_padding=space_padding, line_num=line_num) } fn symbol_table(input: ast::AST, handle: &mut Schala, comp: Option<&mut PassDebugArtifact>) -> Result { let add = handle.symbol_table.borrow_mut().add_top_level_symbols(&input); match add { Ok(()) => { let debug = handle.symbol_table.borrow().debug_symbol_table(); comp.map(|comp| comp.add_artifact(debug)); Ok(input) }, Err(msg) => Err(msg) } } fn typechecking(input: ast::AST, handle: &mut Schala, comp: Option<&mut PassDebugArtifact>) -> Result { let result = handle.type_context.typecheck(&input); comp.map(|comp| { comp.add_artifact(match result { Ok(ty) => ty.to_string(), Err(err) => format!("Type error: {}", err.msg) }); }); Ok(input) } fn ast_reducing(input: ast::AST, handle: &mut Schala, comp: Option<&mut PassDebugArtifact>) -> Result { let ref symbol_table = handle.symbol_table.borrow(); let output = input.reduce(symbol_table); comp.map(|comp| comp.add_artifact(format!("{:?}", output))); Ok(output) } fn eval(input: reduced_ast::ReducedAST, handle: &mut Schala, comp: Option<&mut PassDebugArtifact>) -> Result { comp.map(|comp| comp.add_artifact(handle.state.debug_print())); let evaluation_outputs = handle.state.evaluate(input, true); let text_output: Result, String> = evaluation_outputs .into_iter() .collect(); let eval_output: Result = text_output .map(|v| { v.into_iter().intersperse(format!("\n")).collect() }); eval_output } /// Represents lines of source code struct SourceReference { lines: Option> } impl SourceReference { fn new() -> SourceReference { SourceReference { lines: None } } fn load_new_source(&mut self, source: &str) { //TODO this is a lot of heap allocations - maybe there's a way to make it more efficient? self.lines = Some(source.lines().map(|s| s.to_string()).collect()); } fn get_line(&self, line: usize) -> String { self.lines.as_ref().and_then(|x| x.get(line).map(|s| s.to_string())).unwrap_or(format!("NO LINE FOUND")) } } struct PassDebugArtifact { artifact: Option } impl PassDebugArtifact { fn add_artifact(&mut self, artifact: String) { self.artifact = Some(artifact) } } impl ProgrammingLanguageInterface for Schala { fn get_language_name(&self) -> String { format!("Schala") } fn get_source_file_suffix(&self) -> String { format!("schala") } fn run_computation(&mut self, request: ComputationRequest) -> ComputationResponse { let ComputationRequest { source, debug_requests } = request; let mut token_debug_artifact = None; let mut parsing_debug_artifact = None; let mut symbol_debug_artifact = None; let mut typechecking_debug_artifact = None; let mut reducing_debug_artifact = None; let mut eval_debug_artifact = None; self.source_reference.load_new_source(source); let main_output: Result = tokenizing(source, self, token_debug_artifact) .and_then(|tokens| parsing(tokens, self, parsing_debug_artifact)) .and_then(|ast| symbol_table(ast, self, symbol_debug_artifact)) .and_then(|ast| typechecking(ast, self, typechecking_debug_artifact)) .and_then(|ast| ast_reducing(ast, self, reducing_debug_artifact)) .and_then(|reduced_ast| eval(reduced_ast, self, eval_debug_artifact)); ComputationResponse { main_output, global_output_stats: GlobalOutputStats::default(), debug_responses: vec![], } } fn repl_request(&self, repl_request: String) -> String { format!("Schala: can't understand {}", repl_request) } }