///|
/// Match/case parsing for line blocks.
///|
fn parse_match_stmt(
parser : LineParser,
stmts : Array[Stmt],
line_text : String,
line_index : Int32,
current_indent : Int32,
next_index : Int32,
) -> Result[Bool, ParseError] {
if !line_text.has_prefix("match ") && !line_text.has_prefix("match(") {
return Ok(false)
}
let colon_pos = find_top_level_colon(line_text)
if colon_pos is None {
return Ok(false)
}
let subject_text = substring(line_text, 5, colon_pos.unwrap())
.trim(chars=" \t")
.to_owned()
let subject_tokens = match
tokenize_expr(
subject_text,
parser.index + 1,
parser.offsets[parser.index] + current_indent + 5,
parser.offsets[parser.index] +
current_indent +
5 -
parser.offsets[parser.index] +
1,
) {
Ok(value) => value
Err(err) => return Err(err)
}
let subject_expr = match parse_tokens_expr(subject_tokens) {
Ok(value) => value
Err(err) => return Err(err)
}
parser.index = next_index
let match_indent = parser.peek_next_indent()
if match_indent <= current_indent {
let missing_line = parser.clamp_line_index(
parser.peek_next_significant_index().unwrap_or(parser.index),
)
let token = {
kind: Eof,
start: parser.offsets[missing_line],
end: parser.offsets[missing_line] + 1,
line: missing_line + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Indentation,
"expected indented block".to_string(),
token,
),
)
}
let cases : Array[(Pattern, Expr?, Array[Stmt])] = []
let mut irrefutable_case : (Int32, Int32)? = None
while parser.index < parser.lines.length() {
let case_line = parser.lines[parser.index]
let case_trimmed = case_line.trim(chars=" \t").to_owned()
if case_trimmed.length() == 0 || case_trimmed.has_prefix("#") {
parser.index += 1
continue
}
let case_indent = count_indent(case_line)
if case_indent < match_indent {
break
}
if case_indent > match_indent {
let token = {
kind: Eof,
start: parser.offsets[parser.index] + match_indent,
end: parser.offsets[parser.index] + match_indent + 1,
line: parser.index + 1,
column: match_indent + 1,
}
return Err(
make_parse_error(
ParseErrorKind::Indentation,
"unexpected indent".to_string(),
token,
),
)
}
let mut case_text = substring(case_line, case_indent, case_line.length())
.trim(chars=" \t")
.to_owned()
let mut case_next_index = parser.index + 1
let mut balance = bracket_balance(case_text)
while balance > 0 && case_next_index < parser.lines.length() {
case_text = case_text + "\n" + parser.lines[case_next_index]
case_next_index += 1
balance = bracket_balance(case_text)
}
if !case_text.has_prefix("case ") && !case_text.has_prefix("case(") {
let token = {
kind: Eof,
start: parser.offsets[parser.index] + case_indent,
end: parser.offsets[parser.index] + case_indent + 1,
line: parser.index + 1,
column: case_indent + 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let case_no_comment = strip_inline_comment(case_text)
let colon_pos = find_top_level_colon(case_no_comment)
if colon_pos is None {
let token = {
kind: Eof,
start: parser.offsets[parser.index] + case_indent,
end: parser.offsets[parser.index] + case_indent + 1,
line: parser.index + 1,
column: case_indent + 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let header = substring(case_no_comment, 4, colon_pos.unwrap())
.trim(chars=" \t")
.to_owned()
let mut pattern_text = header
let mut guard_text : String? = None
if header.contains(" if ") {
let parts : Array[String] = []
for part in header.split(" if ") {
parts.push(part.to_owned())
}
if parts.length() >= 2 {
pattern_text = parts[0].trim(chars=" \t").to_owned()
guard_text = Some(parts[1].trim(chars=" \t").to_owned())
}
}
if irrefutable_case is Some((case_index, case_offset)) {
return Err(pattern_syntax_error(case_index, case_offset))
}
let base_offset = parser.offsets[parser.index] + case_indent + 4
let pattern = match parse_pattern(pattern_text, parser.index, base_offset) {
Ok(value) => value
Err(err) => return Err(err)
}
if !pattern_bindings_unique(pattern, Set([])) {
return Err(pattern_syntax_error(parser.index, base_offset))
}
if guard_text is None && pattern_is_irrefutable(pattern) {
irrefutable_case = Some((parser.index, base_offset))
}
let mut guard_expr : Expr? = None
match guard_text {
Some(text) => {
let tokens = match
tokenize_expr(
text,
parser.index + 1,
base_offset,
base_offset - parser.offsets[parser.index] + 1,
) {
Ok(value) => value
Err(err) => return Err(err)
}
let expr = match parse_tokens_expr(tokens) {
Ok(value) => value
Err(err) => return Err(err)
}
guard_expr = Some(expr)
}
None => ()
}
let mut body : Array[Stmt] = []
let body_text = if case_text.contains("\n") {
"".to_string()
} else {
strip_inline_comment(
substring(
case_no_comment,
colon_pos.unwrap() + 1,
case_no_comment.length(),
).to_string(),
)
.trim(chars=" \t")
.to_owned()
}
parser.index = case_next_index
if body_text.length() > 0 {
let segments = split_statements(body_text)
for segment_pair in segments {
let segment = segment_pair.0
let offset = segment_pair.1
if segment.length() == 0 {
continue
}
let base_offset = parser.offsets[parser.index - 1] +
case_indent +
colon_pos.unwrap() +
1 +
offset
let parsed = match
parse_simple_statement(
segment,
parser.index - 1,
parser.offsets[parser.index - 1],
base_offset,
parser.async_depth > 0,
) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in parsed {
body.push(stmt)
}
}
} else {
let body_indent = parser.peek_next_indent()
if body_indent <= match_indent {
let missing_line = parser.clamp_line_index(
parser.peek_next_significant_index().unwrap_or(parser.index),
)
let token = {
kind: Eof,
start: parser.offsets[missing_line],
end: parser.offsets[missing_line] + 1,
line: missing_line + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Indentation,
"expected indented block".to_string(),
token,
),
)
}
body = match parser.parse_block(body_indent) {
Ok(value) => value
Err(err) => return Err(err)
}
}
cases.push((pattern, guard_expr, body))
}
stmts.push(
wrap_stmt_for_line(
parser.offsets,
line_index,
current_indent,
Stmt::Match(subject=subject_expr, cases~),
),
)
Ok(true)
}