diff --git a/schala-lang/language/src/lib.rs b/schala-lang/language/src/lib.rs index 03ffefb..8afe164 100644 --- a/schala-lang/language/src/lib.rs +++ b/schala-lang/language/src/lib.rs @@ -17,18 +17,6 @@ extern crate schala_repl; extern crate schala_lang_codegen; extern crate ena; -use stopwatch::Stopwatch; - -use std::time::Duration; -use std::cell::RefCell; -use std::rc::Rc; -use std::collections::HashSet; - -use itertools::Itertools; -use schala_repl::{ProgrammingLanguageInterface, -ComputationRequest, ComputationResponse, -LangMetaRequest, LangMetaResponse, GlobalOutputStats, -DebugResponse, DebugAsk}; macro_rules! bx { ($e:expr) => { Box::new($e) } @@ -47,300 +35,6 @@ mod builtin; mod reduced_ast; mod eval; -/// All the 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, -} +mod schala; -impl Schala { - fn handle_docs(&self, source: String) -> LangMetaResponse { - LangMetaResponse::Docs { - doc_string: format!("Schala item `{}` : <>", source) - } - } -} - -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: HashSet::default() }; - s.run_computation(request); - s - } - - fn handle_debug_immediate(&self, request: DebugAsk) -> DebugResponse { - use DebugAsk::*; - match request { - Timing => DebugResponse { ask: Timing, value: format!("Invalid") }, - ByStage { stage_name, token } => match &stage_name[..] { - "symbol-table" => { - let value = self.symbol_table.borrow().debug_symbol_table(); - DebugResponse { - ask: ByStage { stage_name: format!("symbol-table"), token }, - value - } - }, - s => { - DebugResponse { - ask: ByStage { stage_name: s.to_string(), token: None }, - value: format!("Not-implemented") - } - } - } - } - } -} - -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| { - let debug_info = match comp.parsing.as_ref().unwrap_or(&ParsingDebugType::CompactAST) { - ParsingDebugType::CompactAST => format!("{:?}", ast), - ParsingDebugType::ExpandedAST => format!("{:#?}", ast), - ParsingDebugType::Trace => format!("{}", trace[0]) //TODO fix this - }; - comp.add_artifact(debug_info); - }); - 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")) - } -} - -enum ParsingDebugType { - CompactAST, - ExpandedAST, - Trace -} - -#[derive(Default)] -struct PassDebugArtifact { - parsing: Option, - artifacts: Vec - -} -impl PassDebugArtifact { - fn add_artifact(&mut self, artifact: String) { - self.artifacts.push(artifact) - } -} - -fn stage_names() -> Vec<&'static str> { - vec![ - "tokenizing", - "parsing", - "symbol-table", - "typechecking", - "ast-reduction", - "ast-walking-evaluation" - ] -} - - -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 { - struct PassToken<'a> { - schala: &'a mut Schala, - stage_durations: &'a mut Vec<(String, Duration)>, - sw: &'a Stopwatch, - debug_requests: &'a HashSet, - debug_responses: &'a mut Vec, - } - - fn output_wrapper(n: usize, func: F, input: Input, token: &mut PassToken) -> Result - where F: Fn(Input, &mut Schala, Option<&mut PassDebugArtifact>) -> Result - { - let stage_names = stage_names(); - let cur_stage_name = stage_names[n]; - let ask = token.debug_requests.iter().find(|ask| ask.is_for_stage(cur_stage_name)); - let mut debug_artifact = ask.and_then(|ask| match ask { - DebugAsk::ByStage { token, .. } => token.as_ref(), - _ => None - }).map(|token| { - let parsing = if cur_stage_name != "parsing" { - None - } else { - Some(match &token[..] { - "compact" => ParsingDebugType::CompactAST, - "expanded" => ParsingDebugType::ExpandedAST, - "trace" => ParsingDebugType::Trace, - _ => ParsingDebugType::CompactAST, - }) - }; - PassDebugArtifact { parsing, ..Default::default() } - }); - let output = func(input, token.schala, debug_artifact.as_mut()); - - token.stage_durations.push((cur_stage_name.to_string(), token.sw.elapsed())); - if let Some(artifact) = debug_artifact { - for value in artifact.artifacts.into_iter() { - let resp = DebugResponse { ask: ask.unwrap().clone(), value }; - token.debug_responses.push(resp); - } - } - output - } - - let ComputationRequest { source, debug_requests } = request; - self.source_reference.load_new_source(source); - let sw = Stopwatch::start_new(); - let mut stage_durations = Vec::new(); - let mut debug_responses = Vec::new(); - let mut tok = PassToken { schala: self, stage_durations: &mut stage_durations, sw: &sw, debug_requests: &debug_requests, debug_responses: &mut debug_responses }; - - let main_output: Result = Ok(source) - .and_then(|source| output_wrapper(0, tokenizing, source, &mut tok)) - .and_then(|tokens| output_wrapper(1, parsing, tokens, &mut tok)) - .and_then(|ast| output_wrapper(2, symbol_table, ast, &mut tok)) - .and_then(|ast| output_wrapper(3, typechecking, ast, &mut tok)) - .and_then(|ast| output_wrapper(4, ast_reducing, ast, &mut tok)) - .and_then(|reduced_ast| output_wrapper(5, eval, reduced_ast, &mut tok)); - - let total_duration = sw.elapsed(); - let global_output_stats = GlobalOutputStats { - total_duration, stage_durations - }; - - ComputationResponse { - main_output, - global_output_stats, - debug_responses, - } - } - - fn request_meta(&mut self, request: LangMetaRequest) -> LangMetaResponse { - match request { - LangMetaRequest::StageNames => LangMetaResponse::StageNames(stage_names().iter().map(|s| s.to_string()).collect()), - LangMetaRequest::Docs { source } => self.handle_docs(source), - LangMetaRequest::ImmediateDebug(debug_request) => - LangMetaResponse::ImmediateDebug(self.handle_debug_immediate(debug_request)), - LangMetaRequest::Custom { .. } => LangMetaResponse::Custom { kind: format!("not-implemented"), value: format!("") } - } - } -} +pub use schala::Schala; diff --git a/schala-lang/language/src/schala.rs b/schala-lang/language/src/schala.rs new file mode 100644 index 0000000..fd733c7 --- /dev/null +++ b/schala-lang/language/src/schala.rs @@ -0,0 +1,311 @@ +use stopwatch::Stopwatch; + +use std::time::Duration; +use std::cell::RefCell; +use std::rc::Rc; +use std::collections::HashSet; + +use itertools::Itertools; +use schala_repl::{ProgrammingLanguageInterface, +ComputationRequest, ComputationResponse, +LangMetaRequest, LangMetaResponse, GlobalOutputStats, +DebugResponse, DebugAsk}; +use crate::{ast, reduced_ast, tokenizing, parsing, eval, typechecking, symbol_table}; + +/// All the 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 handle_docs(&self, source: String) -> LangMetaResponse { + LangMetaResponse::Docs { + doc_string: format!("Schala item `{}` : <>", source) + } + } +} + +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: HashSet::default() }; + s.run_computation(request); + s + } + + fn handle_debug_immediate(&self, request: DebugAsk) -> DebugResponse { + use DebugAsk::*; + match request { + Timing => DebugResponse { ask: Timing, value: format!("Invalid") }, + ByStage { stage_name, token } => match &stage_name[..] { + "symbol-table" => { + let value = self.symbol_table.borrow().debug_symbol_table(); + DebugResponse { + ask: ByStage { stage_name: format!("symbol-table"), token }, + value + } + }, + s => { + DebugResponse { + ask: ByStage { stage_name: s.to_string(), token: None }, + value: format!("Not-implemented") + } + } + } + } + } +} + +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| { + let debug_info = match comp.parsing.as_ref().unwrap_or(&ParsingDebugType::CompactAST) { + ParsingDebugType::CompactAST => format!("{:?}", ast), + ParsingDebugType::ExpandedAST => format!("{:#?}", ast), + ParsingDebugType::Trace => format!("{}", trace[0]) //TODO fix this + }; + comp.add_artifact(debug_info); + }); + 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")) + } +} + +enum ParsingDebugType { + CompactAST, + ExpandedAST, + Trace +} + +#[derive(Default)] +struct PassDebugArtifact { + parsing: Option, + artifacts: Vec + +} +impl PassDebugArtifact { + fn add_artifact(&mut self, artifact: String) { + self.artifacts.push(artifact) + } +} + +fn stage_names() -> Vec<&'static str> { + vec![ + "tokenizing", + "parsing", + "symbol-table", + "typechecking", + "ast-reduction", + "ast-walking-evaluation" + ] +} + + +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 { + struct PassToken<'a> { + schala: &'a mut Schala, + stage_durations: &'a mut Vec<(String, Duration)>, + sw: &'a Stopwatch, + debug_requests: &'a HashSet, + debug_responses: &'a mut Vec, + } + + fn output_wrapper(n: usize, func: F, input: Input, token: &mut PassToken) -> Result + where F: Fn(Input, &mut Schala, Option<&mut PassDebugArtifact>) -> Result + { + let stage_names = stage_names(); + let cur_stage_name = stage_names[n]; + let ask = token.debug_requests.iter().find(|ask| ask.is_for_stage(cur_stage_name)); + let mut debug_artifact = ask.and_then(|ask| match ask { + DebugAsk::ByStage { token, .. } => token.as_ref(), + _ => None + }).map(|token| { + let parsing = if cur_stage_name != "parsing" { + None + } else { + Some(match &token[..] { + "compact" => ParsingDebugType::CompactAST, + "expanded" => ParsingDebugType::ExpandedAST, + "trace" => ParsingDebugType::Trace, + _ => ParsingDebugType::CompactAST, + }) + }; + PassDebugArtifact { parsing, ..Default::default() } + }); + let output = func(input, token.schala, debug_artifact.as_mut()); + + token.stage_durations.push((cur_stage_name.to_string(), token.sw.elapsed())); + if let Some(artifact) = debug_artifact { + for value in artifact.artifacts.into_iter() { + let resp = DebugResponse { ask: ask.unwrap().clone(), value }; + token.debug_responses.push(resp); + } + } + output + } + + let ComputationRequest { source, debug_requests } = request; + self.source_reference.load_new_source(source); + let sw = Stopwatch::start_new(); + let mut stage_durations = Vec::new(); + let mut debug_responses = Vec::new(); + let mut tok = PassToken { schala: self, stage_durations: &mut stage_durations, sw: &sw, debug_requests: &debug_requests, debug_responses: &mut debug_responses }; + + let main_output: Result = Ok(source) + .and_then(|source| output_wrapper(0, tokenizing, source, &mut tok)) + .and_then(|tokens| output_wrapper(1, parsing, tokens, &mut tok)) + .and_then(|ast| output_wrapper(2, symbol_table, ast, &mut tok)) + .and_then(|ast| output_wrapper(3, typechecking, ast, &mut tok)) + .and_then(|ast| output_wrapper(4, ast_reducing, ast, &mut tok)) + .and_then(|reduced_ast| output_wrapper(5, eval, reduced_ast, &mut tok)); + + let total_duration = sw.elapsed(); + let global_output_stats = GlobalOutputStats { + total_duration, stage_durations + }; + + ComputationResponse { + main_output, + global_output_stats, + debug_responses, + } + } + + fn request_meta(&mut self, request: LangMetaRequest) -> LangMetaResponse { + match request { + LangMetaRequest::StageNames => LangMetaResponse::StageNames(stage_names().iter().map(|s| s.to_string()).collect()), + LangMetaRequest::Docs { source } => self.handle_docs(source), + LangMetaRequest::ImmediateDebug(debug_request) => + LangMetaResponse::ImmediateDebug(self.handle_debug_immediate(debug_request)), + LangMetaRequest::Custom { .. } => LangMetaResponse::Custom { kind: format!("not-implemented"), value: format!("") } + } + } +}