mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 23:31:12 +00:00
334 lines
12 KiB
Rust
334 lines
12 KiB
Rust
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<SyntaxError<'a>>,
|
|
) -> 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<char>),
|
|
}
|
|
|
|
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,
|
|
}
|
|
}
|