use roc_parse::parser::{Col, ParseProblem, Row, SyntaxError}; use roc_region::all::Region; use std::path::PathBuf; use crate::report::{Report, RocDocAllocator}; use ven_pretty::DocAllocator; pub fn parse_problem<'a>( alloc: &'a RocDocAllocator<'a>, filename: PathBuf, starting_line: u32, parse_problem: ParseProblem>, ) -> Report<'a> { let line = starting_line + parse_problem.line; let region = Region { start_line: line, end_line: line, start_col: parse_problem.column, end_col: parse_problem.column + 1, }; let report = |doc| Report { filename: filename.clone(), doc, title: "PARSE PROBLEM".to_string(), }; use SyntaxError::*; match parse_problem.problem { SyntaxError::ConditionFailed => { let doc = alloc.stack(vec![ alloc.reflow("A condition failed:"), alloc.region(region), ]); Report { filename, doc, title: "PARSE PROBLEM".to_string(), } } SyntaxError::ArgumentsBeforeEquals(region) => { let doc = alloc.stack(vec![ alloc.reflow("Unexpected tokens in front of the `=` symbol:"), alloc.region(region), ]); Report { filename, doc, title: "PARSE PROBLEM".to_string(), } } Unexpected(mut region) => { if region.start_col == region.end_col { region.end_col += 1; } let doc = alloc.stack(vec![ alloc.concat(vec![ alloc.reflow("Unexpected token "), // context(alloc, &parse_problem.context_stack, "here"), alloc.text(":"), ]), alloc.region(region), ]); report(doc) } Type(typ) => to_type_report(alloc, filename, starting_line, typ), _ => todo!("unhandled parse error: {:?}", parse_problem.problem), } } fn to_type_report<'a>( alloc: &'a RocDocAllocator<'a>, filename: PathBuf, starting_line: u32, parse_problem: roc_parse::parser::Type<'a>, ) -> Report<'a> { use roc_parse::parser::Type; match parse_problem { Type::TRecord(record, row, col) => { to_trecord_report(alloc, filename, starting_line, record, row, col) } _ => todo!("unhandled type parse error: {:?}", &parse_problem), } } fn to_trecord_report<'a>( alloc: &'a RocDocAllocator<'a>, filename: PathBuf, _starting_line: u32, parse_problem: roc_parse::parser::TRecord<'a>, start_row: Row, start_col: Col, ) -> Report<'a> { use roc_parse::parser::TRecord; match parse_problem { TRecord::Open(row, col) => match what_is_next(alloc.src_lines, row, col) { Next::Keyword(keyword) => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let region = to_keyword_region(row, col, keyword); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record type, but I got stuck on this field name:"), alloc.region_with_subregion(surroundings, region), alloc.concat(vec![ alloc.reflow(r"Looks like you are trying to use "), alloc.keyword(keyword), alloc.reflow(" as a field name, but that is a reserved word. Try using a different name!"), ]), ]); Report { filename: filename.clone(), doc, title: "UNFINISHED RECORD TYPE".to_string(), } } _ => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let region = Region::from_row_col(row, col); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record type, but I got stuck here:"), alloc.region_with_subregion(surroundings, region), alloc.concat(vec![ alloc.reflow(r"Record types look like "), alloc.parser_suggestion("{ name : String, age : Int },"), alloc.reflow(" so I was expecting to see a field name next."), ]), ]); Report { filename: filename.clone(), doc, title: "UNFINISHED RECORD TYPE".to_string(), } } }, TRecord::End(row, col) => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let region = Region::from_row_col(row, col); let doc = alloc.stack(vec![ alloc.reflow("I am partway through parsing a record type, but I got stuck here:"), alloc.region_with_subregion(surroundings, region), alloc.concat(vec![ alloc.reflow( r"I was expecting to see a closing curly brace before this, so try adding a ", ), alloc.parser_suggestion("}"), alloc.reflow(" and see if that helps?"), ]), ]); Report { filename: filename.clone(), doc, title: "UNFINISHED RECORD TYPE".to_string(), } } TRecord::Field(row, col) => match what_is_next(alloc.src_lines, row, col) { Next::Keyword(keyword) => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let region = to_keyword_region(row, col, keyword); let doc = alloc.stack(vec![ alloc.reflow(r"I just started parsing a record type, but I got stuck on this field name:"), alloc.region_with_subregion(surroundings, region), alloc.concat(vec![ alloc.reflow(r"Looks like you are trying to use "), alloc.keyword(keyword), alloc.reflow(" as a field name, but that is a reserved word. Try using a different name!"), ]), ]); Report { filename: filename.clone(), doc, title: "UNFINISHED RECORD TYPE".to_string(), } } Next::Other(Some(',')) => todo!(), Next::Other(Some('}')) => unreachable!("or is it?"), _ => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let region = Region::from_row_col(row, col); let doc = alloc.stack(vec![ alloc.reflow(r"I am partway through parsing a record type, but I got stuck here:"), alloc.region_with_subregion(surroundings, region), alloc.concat(vec![ alloc.reflow(r"I was expecting to see another record field defined next, so I am looking for a name like "), alloc.parser_suggestion("userName"), alloc.reflow(" or "), alloc.parser_suggestion("plantHight"), alloc.reflow("."), ]), ]); Report { filename: filename.clone(), doc, title: "PROBLEM IN RECORD TYPE".to_string(), } } }, TRecord::IndentEnd(row, col) => { match next_line_starts_with_close_curly(alloc.src_lines, row - 1) { Some((curly_row, curly_col)) => { let surroundings = Region::from_rows_cols(start_row, start_col, curly_row, curly_col); let region = Region::from_row_col(curly_row, curly_col); let doc = alloc.stack(vec![ alloc.reflow( "I am partway through parsing a record type, but I got stuck here:", ), alloc.region_with_subregion(surroundings, region), alloc.concat(vec![ alloc.reflow("I need this curly brace to be indented more. Try adding more spaces before it!"), ]), ]); Report { filename: filename.clone(), doc, title: "NEED MORE INDENTATION".to_string(), } } None => { let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let region = Region::from_row_col(row, col); let doc = alloc.stack(vec![ alloc.reflow( r"I am partway through parsing a record type, but I got stuck here:", ), alloc.region_with_subregion(surroundings, region), alloc.concat(vec![ alloc.reflow("I was expecting to see a closing curly "), alloc.reflow("brace before this, so try adding a "), alloc.parser_suggestion("}"), alloc.reflow(" and see if that helps?"), ]), ]); Report { filename: filename.clone(), doc, title: "UNFINISHED RECORD TYPE".to_string(), } } } } _ => todo!("unhandled record type parse error: {:?}", &parse_problem), } } enum Next<'a> { Keyword(&'a str), // Operator(&'a str), Close(&'a str, char), Other(Option), } fn what_is_next<'a>(source_lines: &'a [&'a str], row: Row, col: Col) -> Next<'a> { let row_index = row as usize; let col_index = col as usize; match source_lines.get(row_index) { None => Next::Other(None), Some(line) => { let chars = &line[col_index..]; match roc_parse::keyword::KEYWORDS .iter() .find(|keyword| starts_with_keyword(chars, keyword)) { Some(keyword) => Next::Keyword(keyword), None => match chars.chars().nth(0) { None => Next::Other(None), Some(c) => match c { ')' => Next::Close("parenthesis", ')'), ']' => Next::Close("square bracket", ']'), '}' => Next::Close("curly brace", '}'), // _ if is_symbol(c) => todo!("it's an operator"), _ => Next::Other(Some(c)), }, }, } } } } fn starts_with_keyword(rest_of_line: &str, keyword: &str) -> bool { if rest_of_line.starts_with(keyword) { match (&rest_of_line[keyword.len()..]).chars().nth(0) { None => true, Some(c) => !c.is_alphanumeric(), } } else { false } } fn next_line_starts_with_close_curly(source_lines: &[&str], row: Row) -> Option<(Row, Col)> { match source_lines.get(row as usize + 1) { None => None, Some(line) => { let spaces_dropped = line.trim_start_matches(' '); match spaces_dropped.chars().nth(0) { Some('}') => Some((row + 1, (line.len() - spaces_dropped.len()) as u16)), _ => None, } } } } fn to_keyword_region(row: Row, col: Col, keyword: &str) -> Region { Region { start_line: row, start_col: col, end_line: row, end_col: col + keyword.len() as u16, } }