///|
/// Line/block parser for moonpython.
///
/// Implementation is split across:
/// - parser_line_core.mbt
/// - parser_line_match.mbt
/// - parser_line_defs.mbt
///
///|
fn LineParser::parse_block(
self : LineParser,
indent : Int32,
) -> Result[Array[Stmt], ParseError] {
let stmts : Array[Stmt] = []
let mut pending_decorators : Array[Expr] = []
let mut pending_decorator_token : Token? = None
while self.index < self.lines.length() {
let line = self.lines[self.index]
let trimmed = line.trim(chars=" \t").to_owned()
if trimmed.length() == 0 || trimmed.has_prefix("#") {
self.index += 1
continue
}
if trimmed == "\\" {
self.index += 1
continue
}
let leading_chars = line.to_array()
let mut idx = 0
let mut saw_space = false
let mut saw_tab = false
while idx < leading_chars.length() {
let c = leading_chars[idx]
if c == ' ' {
saw_space = true
} else if c == '\t' {
saw_tab = true
} else {
break
}
idx += 1
}
if saw_space && saw_tab {
let token = {
kind: Eof,
start: self.offsets[self.index],
end: self.offsets[self.index] + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Indentation,
"inconsistent use of tabs and spaces in indentation".to_string(),
token,
),
)
}
let current_indent = count_indent(line)
if current_indent < indent {
if pending_decorators.length() > 0 {
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
pending_decorator_token.unwrap(),
),
)
}
// Remember the indent level we just dedented from. If the next statement
// is indented to a level that doesn't match any active block, CPython
// reports it as "unindent does not match any outer indentation level".
self.last_dedent_from = Some(indent)
break
}
if current_indent > indent {
match self.last_dedent_from {
Some(prev_indent) =>
if current_indent < prev_indent {
let error_start = if current_indent > 0 {
current_indent - 1
} else {
0
}
let token = {
kind: Eof,
start: self.offsets[self.index] + error_start,
end: self.offsets[self.index] + error_start + 1,
line: self.index + 1,
column: current_indent,
}
return Err(
make_parse_error(
ParseErrorKind::Indentation,
"unindent does not match any outer indentation level".to_string(),
token,
),
)
}
None => ()
}
let error_start = if current_indent > 0 { current_indent - 1 } else { 0 }
let token = {
kind: Eof,
start: self.offsets[self.index] + error_start,
end: self.offsets[self.index] + error_start + 1,
line: self.index + 1,
column: current_indent,
}
return Err(
make_parse_error(
ParseErrorKind::Indentation,
"unexpected indent".to_string(),
token,
),
)
}
// At a stable indentation level, clear any dedent marker.
self.last_dedent_from = None
let line_index = self.index
let raw_line = substring(line, current_indent, line.length()).to_string()
let mut line_text = raw_line.trim(chars=" \t").to_owned()
if find_triple_quote(raw_line) is None {
line_text = strip_inline_comment(line_text).trim(chars=" \t").to_owned()
}
if line_text.has_prefix("async for(") {
line_text = "async for " + substring(line_text, 9, line_text.length())
}
if line_text.has_prefix("for(") {
line_text = "for " + substring(line_text, 3, line_text.length())
}
if line_text.has_prefix("while(") {
line_text = "while " + substring(line_text, 5, line_text.length())
}
let mut next_index = self.index + 1
match unclosed_triple_quote(raw_line) {
None => ()
Some(_) => {
let mut combined = raw_line
let mut idx = self.index + 1
while idx < self.lines.length() {
let next_line = self.lines[idx]
combined = combined + "\n" + next_line
idx += 1
if unclosed_triple_quote(combined) is None {
next_index = idx
line_text = combined
break
}
}
if idx == self.lines.length() &&
unclosed_triple_quote(combined) is Some(_) {
line_text = combined
next_index = idx
}
}
}
while has_unclosed_string_line_continuation(line_text) &&
next_index < self.lines.length() {
// Backslash-newline inside a non-triple-quoted string literal (e.g.
// `"foo\\\nbar"`). Join physical lines so the lexer can treat the
// `\\\n` pair as a line continuation inside the string.
let next_line = self.lines[next_index]
line_text = line_text + "\n" + next_line
next_index += 1
}
while has_trailing_line_continuation(line_text) &&
next_index < self.lines.length() {
let next_line = self.lines[next_index]
// For explicit `\\` line continuations we drop the backslash and join
// with a space. Keeping raw newlines here would break statement/header
// parsing for `def ... \\` signatures in the stdlib (e.g. Lib/traceback.py).
line_text = substring(line_text, 0, line_text.length() - 1)
.trim(chars=" \t")
.to_owned()
let appended = if find_triple_quote(next_line) is Some(_) {
next_line
} else {
strip_inline_comment(next_line)
}
line_text = line_text + " " + appended
next_index += 1
line_text = line_text.trim(chars=" \t").to_owned()
}
match unclosed_triple_quote(line_text) {
None => ()
Some(_) => {
let mut combined = line_text
let mut idx = next_index
while idx < self.lines.length() {
let next_line = self.lines[idx]
combined = combined + "\n" + next_line
idx += 1
if unclosed_triple_quote(combined) is None {
next_index = idx
line_text = combined
break
}
}
if idx == self.lines.length() &&
unclosed_triple_quote(combined) is Some(_) {
line_text = combined
next_index = idx
}
}
}
let mut balance = bracket_balance(line_text)
while balance > 0 && next_index < self.lines.length() {
let next_line = self.lines[next_index]
let appended = if find_triple_quote(next_line) is Some(_) {
next_line
} else {
strip_inline_comment(next_line)
}
line_text = line_text + "\n" + appended
next_index += 1
balance = bracket_balance(line_text)
}
if find_triple_quote(line_text) is None {
// Preserve physical newlines so the lexer can track correct token
// line/column offsets for multi-line expressions (PEP 657 caret tests).
line_text = line_text.trim(chars=" \t").to_owned()
}
if pending_decorators.length() > 0 &&
!line_text.has_prefix("@") &&
!line_text.has_prefix("def ") &&
!line_text.has_prefix("async def ") &&
!line_text.has_prefix("class ") {
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
pending_decorator_token.unwrap(),
),
)
}
if has_keyword_prefix(line_text, "return") && self.func_depth == 0 {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
if self.func_depth == 0 &&
line_has_yield_keyword(line_text) &&
!line_has_lambda_keyword(line_text) &&
!line_text.has_prefix("def ") &&
!line_text.has_prefix("async def ") {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
// `await` legality is checked after parsing the whole block, so we can
// allow `await` inside generator expressions in sync code.
let handled = match
parse_match_stmt(
self, stmts, line_text, line_index, current_indent, next_index,
) {
Ok(value) => value
Err(err) => return Err(err)
}
if handled {
continue
}
if line_text.has_prefix("@") {
let decorator_line_index = self.index
let mut expr_text = substring(line_text, 1, line_text.length())
.trim(chars=" \t")
.to_owned()
if expr_text.length() == 0 {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let mut decorator_next_index = next_index
while has_trailing_line_continuation(expr_text) &&
decorator_next_index < self.lines.length() {
expr_text = substring(expr_text, 0, expr_text.length() - 1)
.trim(chars=" \t")
.to_owned()
let next_line = self.lines[decorator_next_index]
let appended = if find_triple_quote(next_line) is Some(_) {
next_line
} else {
strip_inline_comment(next_line)
}
expr_text = expr_text + " " + appended
decorator_next_index += 1
expr_text = expr_text.trim(chars=" \t").to_owned()
}
let mut balance = bracket_balance(expr_text)
while balance > 0 && decorator_next_index < self.lines.length() {
let next_line = self.lines[decorator_next_index]
let appended = if find_triple_quote(next_line) is Some(_) {
next_line
} else {
strip_inline_comment(next_line)
}
expr_text = expr_text + "\n" + appended
decorator_next_index += 1
balance = bracket_balance(expr_text)
}
if find_triple_quote(expr_text) is None {
expr_text = expr_text.trim(chars=" \t").to_owned()
}
let tokens = match
tokenize_expr(
expr_text,
decorator_line_index + 1,
self.offsets[decorator_line_index] + current_indent + 1,
current_indent + 2,
) {
Ok(value) => value
Err(err) => return Err(err)
}
let expr = match parse_tokens_expr(tokens) {
Ok(value) => value
Err(err) => return Err(err)
}
pending_decorators.push(expr)
if pending_decorator_token is None {
pending_decorator_token = Some({
kind: Eof,
start: self.offsets[decorator_line_index] + current_indent,
end: self.offsets[decorator_line_index] + current_indent + 1,
line: decorator_line_index + 1,
column: 1,
})
}
self.index = decorator_next_index
continue
}
let (handled, next_pending, next_token) = match
parse_class_stmt(
self, stmts, pending_decorators, pending_decorator_token, line_text, line_index,
current_indent, next_index,
) {
Ok(value) => value
Err(err) => return Err(err)
}
pending_decorators = next_pending
pending_decorator_token = next_token
if handled {
continue
}
if (
line_text.has_prefix("async for ") ||
line_text.has_prefix("async with ")
) &&
self.async_depth == 0 {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
if line_text.has_prefix("async for ") {
let colon_pos = find_top_level_colon(line_text)
if colon_pos is None {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let mut header_text = substring(line_text, 10, colon_pos.unwrap())
.trim(chars=" \t")
.to_owned()
header_text = strip_wrapping_parens(header_text)
let in_pos = header_text.find(" in ")
if in_pos is None {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let pos = in_pos.unwrap()
let target_text = substring(header_text, 0, pos)
.trim(chars=" \t")
.to_owned()
let iter_text = substring(header_text, pos + 4, header_text.length())
.trim(chars=" \t")
.to_owned()
if target_text.length() == 0 {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let target_tokens = match
tokenize_expr(
target_text,
self.index + 1,
self.offsets[self.index] + current_indent + 10,
current_indent + 11,
) {
Ok(value) => value
Err(err) => return Err(err)
}
let target_token = {
kind: Eof,
start: self.offsets[self.index] + current_indent + 10,
end: self.offsets[self.index] + current_indent + 11,
line: self.index + 1,
column: 1,
}
// `for` targets allow starred assignment targets like `for a, *_ in xs:`.
let target = match
target_from_assignment_tokens(target_tokens, target_token) {
Ok(value) => value
Err(err) => return Err(err)
}
let iter_tokens = match
tokenize_expr(
iter_text,
self.index + 1,
self.offsets[self.index] + current_indent + 10 + pos + 4,
current_indent + 10 + pos + 4 + 1,
) {
Ok(value) => value
Err(err) => return Err(err)
}
let iter_expr = match parse_tokens_expr(iter_tokens) {
Ok(value) => value
Err(err) => return Err(err)
}
let raw_body_text = substring(
line_text,
colon_pos.unwrap() + 1,
line_text.length(),
).to_string()
let body_leading = count_indent(raw_body_text)
let body_text = raw_body_text.trim(chars=" \t").to_owned()
let body : Array[Stmt] = []
if body_text.length() > 0 {
let base_offset = self.offsets[self.index] +
current_indent +
colon_pos.unwrap() +
1 +
body_leading
let parsed = match
parse_simple_statement(
body_text,
self.index,
self.offsets[self.index],
base_offset,
self.async_depth > 0,
) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in parsed {
body.push(stmt)
}
self.index = next_index
} else {
self.index = next_index
let next_indent = self.peek_next_indent()
if next_indent <= current_indent {
let missing_line = self.clamp_line_index(
self.peek_next_significant_index().unwrap_or(self.index),
)
let token = {
kind: Eof,
start: self.offsets[missing_line],
end: self.offsets[missing_line] + 1,
line: missing_line + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Indentation,
"expected indented block".to_string(),
token,
),
)
}
let parsed = match self.parse_block(next_indent) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in parsed {
body.push(stmt)
}
}
let else_body : Array[Stmt] = []
if self.index < self.lines.length() {
let else_line = self.lines[self.index]
let else_trimmed = else_line.trim(chars=" \t").to_owned()
let else_indent = count_indent(else_line)
if else_indent == current_indent && else_trimmed.has_prefix("else") {
let colon_pos = find_top_level_colon(else_trimmed)
if colon_pos is None {
let token = {
kind: Eof,
start: self.offsets[self.index] + else_indent,
end: self.offsets[self.index] + else_indent + 1,
line: self.index + 1,
column: else_indent + 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let inline_text = match colon_pos {
Some(pos) =>
strip_inline_comment(
substring(else_trimmed, pos + 1, else_trimmed.length()).to_string(),
)
.trim(chars=" \t")
.to_owned()
None => ""
}
self.index += 1
if inline_text.length() > 0 {
let segments = split_statements(inline_text)
for segment_pair in segments {
let segment = segment_pair.0
let offset = segment_pair.1
if segment.length() == 0 {
continue
}
let parsed = match
parse_simple_statement(
segment,
self.index - 1,
self.offsets[self.index - 1],
self.offsets[self.index - 1] +
else_indent +
colon_pos.unwrap_or(0) +
1 +
offset,
self.async_depth > 0,
) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in parsed {
else_body.push(stmt)
}
}
} else {
let block_indent = self.peek_next_indent()
if block_indent <= current_indent {
let missing_line = self.clamp_line_index(
self.peek_next_significant_index().unwrap_or(self.index),
)
let token = {
kind: Eof,
start: self.offsets[missing_line],
end: self.offsets[missing_line] + 1,
line: missing_line + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Indentation,
"expected indented block".to_string(),
token,
),
)
}
let parsed = match self.parse_block(block_indent) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in parsed {
else_body.push(stmt)
}
}
}
}
stmts.push(
wrap_stmt_for_line(
self.offsets,
line_index,
current_indent,
Stmt::AsyncFor(target~, iter=iter_expr, body~, else_body~),
),
)
continue
}
if line_text.has_prefix("async with ") {
let mut full_line_text = line_text
let mut colon_pos = find_top_level_colon(full_line_text)
let mut header_end_index = next_index
if colon_pos is None {
let mut scan = next_index
while scan < self.lines.length() {
let part = self.lines[scan].trim(chars=" \t").to_owned()
full_line_text = full_line_text + "\n" + part
colon_pos = find_top_level_colon(full_line_text)
scan += 1
if colon_pos is Some(_) {
header_end_index = scan
break
}
}
}
if colon_pos is None {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let header_text = substring(full_line_text, 10, colon_pos.unwrap())
.trim(chars=" \t")
.to_owned()
let mut item_text = header_text
if item_text.has_prefix("(") && item_text.has_suffix(")") {
item_text = substring(item_text, 1, item_text.length() - 1).to_string()
}
let with_items : Array[(Expr, String?)] = []
for raw_item in split_top_level_commas(item_text) {
let item = raw_item.trim(chars=" \t\n\r").to_owned()
if item.length() == 0 {
// Allow trailing commas inside `async with (...)`.
continue
}
let mut context_text = item
let mut target : String? = None
match find_top_level_as_pos(item) {
Some(pos) => {
context_text = substring(item, 0, pos)
.trim(chars=" \t\n\r")
.to_owned()
let mut name_text = substring(item, pos + 2, item.length())
.trim(chars=" \t\n\r")
.to_owned()
let comma_pos = find_char(name_text, ',')
if comma_pos is Some(pos) {
name_text = substring(name_text, 0, pos)
.trim(chars=" \t")
.to_owned()
}
let close_pos = find_char(name_text, ')')
if close_pos is Some(pos) {
name_text = substring(name_text, 0, pos)
.trim(chars=" \t")
.to_owned()
}
if name_text.length() > 0 {
target = Some(name_text)
}
}
None => ()
}
let context_tokens = match
tokenize_expr(
context_text,
self.index + 1,
self.offsets[self.index] + current_indent + 10,
current_indent + 11,
) {
Ok(value) => value
Err(err) => return Err(err)
}
let context = match parse_tokens_expr(context_tokens) {
Ok(value) => value
Err(err) => return Err(err)
}
with_items.push((context, target))
}
if with_items.length() == 0 {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let raw_body_text = substring(
full_line_text,
colon_pos.unwrap() + 1,
full_line_text.length(),
).to_string()
let body_leading = count_indent(raw_body_text)
let body_text = raw_body_text.trim(chars=" \t").to_owned()
if body_text.length() > 0 {
let body : Array[Stmt] = []
let base_offset = self.offsets[self.index] +
current_indent +
colon_pos.unwrap() +
1 +
body_leading
let parsed = match
parse_simple_statement(
body_text,
self.index,
self.offsets[self.index],
base_offset,
self.async_depth > 0,
) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in parsed {
body.push(stmt)
}
let mut nested_body = body
for i = with_items.length() - 1; i >= 0; i = i - 1 {
let (context, target) = with_items[i]
nested_body = [
wrap_stmt_for_line(
self.offsets,
line_index,
current_indent,
Stmt::AsyncWith(context~, target~, body=nested_body),
),
]
}
stmts.push(nested_body[0])
self.index = header_end_index
continue
}
self.index = header_end_index
let next_indent = self.peek_next_indent()
if next_indent <= current_indent {
let missing_line = self.clamp_line_index(
self.peek_next_significant_index().unwrap_or(self.index),
)
let token = {
kind: Eof,
start: self.offsets[missing_line],
end: self.offsets[missing_line] + 1,
line: missing_line + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Indentation,
"expected indented block".to_string(),
token,
),
)
}
let body = match self.parse_block(next_indent) {
Ok(value) => value
Err(err) => return Err(err)
}
let mut nested_body = body
for i = with_items.length() - 1; i >= 0; i = i - 1 {
let (context, target) = with_items[i]
nested_body = [
wrap_stmt_for_line(
self.offsets,
line_index,
current_indent,
Stmt::AsyncWith(context~, target~, body=nested_body),
),
]
}
stmts.push(nested_body[0])
continue
}
let (handled, next_pending, next_token) = match
parse_def_stmt(
self, stmts, pending_decorators, pending_decorator_token, line_text, line,
line_index, current_indent, next_index,
) {
Ok(value) => value
Err(err) => return Err(err)
}
pending_decorators = next_pending
pending_decorator_token = next_token
if handled {
continue
}
if line_text.has_prefix("if ") || line_text.has_prefix("if(") {
let if_line_index = self.index
let if_indent = current_indent
let colon_pos = find_top_level_colon(line_text)
if colon_pos is None {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let condition_text = substring(line_text, 2, colon_pos.unwrap())
.trim(chars=" \t")
.to_owned()
let condition_tokens = match
tokenize_expr(
condition_text,
self.index + 1,
self.offsets[self.index] + current_indent + 2,
current_indent + 3,
) {
Ok(value) => value
Err(err) => return Err(err)
}
let condition = match parse_tokens_expr(condition_tokens) {
Ok(value) => value
Err(err) => return Err(err)
}
let raw_body_text = substring(
line_text,
colon_pos.unwrap() + 1,
line_text.length(),
).to_string()
let body_leading = count_indent(raw_body_text)
let body_text = raw_body_text.trim(chars=" \t").to_owned()
self.index = next_index
let mut body : Array[Stmt] = []
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
}
// Inline suite statements belong to the physical `if` line (even when
// the header spans multiple lines due to bracket continuation).
let base_offset = self.offsets[if_line_index] +
if_indent +
colon_pos.unwrap() +
1 +
body_leading +
offset
let parsed = match
parse_simple_statement(
segment,
if_line_index,
self.offsets[if_line_index],
base_offset,
self.async_depth > 0,
) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in parsed {
body.push(stmt)
}
}
} else {
let next_indent = self.peek_next_indent()
if next_indent <= current_indent {
let missing_line = self.clamp_line_index(
self.peek_next_significant_index().unwrap_or(self.index),
)
let token = {
kind: Eof,
start: self.offsets[missing_line],
end: self.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 self.parse_block(next_indent) {
Ok(value) => value
Err(err) => return Err(err)
}
}
let clauses : Array[(Expr, Array[Stmt])] = [(condition, body)]
let mut else_body : Array[Stmt] = []
while self.index < self.lines.length() {
let next_line = self.lines[self.index]
let next_trimmed = next_line.trim(chars=" \t").to_owned()
if next_trimmed.length() == 0 || next_trimmed.has_prefix("#") {
self.index += 1
continue
}
let next_indent = count_indent(next_line)
if next_indent != current_indent {
break
}
let mut next_text = substring(
next_line,
next_indent,
next_line.length(),
)
.trim(chars=" \t")
.to_owned()
if find_triple_quote(next_text) is None {
next_text = strip_inline_comment(next_text)
.trim(chars=" \t")
.to_owned()
}
if next_text.has_prefix("elif ") || next_text.has_prefix("elif(") {
let elif_line_index = self.index
let mut elif_text = next_text
let mut elif_next_index = self.index + 1
while has_trailing_line_continuation(elif_text) &&
elif_next_index < self.lines.length() {
elif_text = substring(elif_text, 0, elif_text.length() - 1)
.trim(chars=" \t")
.to_owned()
let next_line = self.lines[elif_next_index]
let appended = if find_triple_quote(next_line) is Some(_) {
next_line
} else {
strip_inline_comment(next_line)
}
elif_text = elif_text + " " + appended
elif_next_index += 1
elif_text = elif_text.trim(chars=" \t").to_owned()
}
let mut balance = bracket_balance(elif_text)
while balance > 0 && elif_next_index < self.lines.length() {
let next_line = self.lines[elif_next_index]
let appended = if find_triple_quote(next_line) is Some(_) {
next_line
} else {
strip_inline_comment(next_line)
}
elif_text = elif_text + "\n" + appended
elif_next_index += 1
balance = bracket_balance(elif_text)
}
if find_triple_quote(elif_text) is None {
elif_text = elif_text.trim(chars=" \t").to_owned()
}
let colon_pos = find_top_level_colon(elif_text)
if colon_pos is None {
let token = {
kind: Eof,
start: self.offsets[self.index] + next_indent,
end: self.offsets[self.index] + next_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let cond_text = substring(elif_text, 4, colon_pos.unwrap())
.trim(chars=" \t")
.to_owned()
let cond_tokens = match
tokenize_expr(
cond_text,
self.index + 1,
self.offsets[self.index] + next_indent + 4,
next_indent + 5,
) {
Ok(value) => value
Err(err) => return Err(err)
}
let cond = match parse_tokens_expr(cond_tokens) {
Ok(value) => value
Err(err) => return Err(err)
}
let raw_body_text = substring(
elif_text,
colon_pos.unwrap() + 1,
elif_text.length(),
).to_string()
let body_leading = count_indent(raw_body_text)
let body_text = raw_body_text.trim(chars=" \t").to_owned()
self.index = elif_next_index
if body_text.length() > 0 {
let elif_body : Array[Stmt] = []
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
}
// Inline suite statements belong to the physical `elif` line.
let base_offset = self.offsets[elif_line_index] +
next_indent +
colon_pos.unwrap() +
1 +
body_leading +
offset
let parsed = match
parse_simple_statement(
segment,
elif_line_index,
self.offsets[elif_line_index],
base_offset,
self.async_depth > 0,
) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in parsed {
elif_body.push(stmt)
}
}
clauses.push((cond, elif_body))
continue
}
let elif_indent = self.peek_next_indent()
if elif_indent <= next_indent {
let missing_line = self.clamp_line_index(
self.peek_next_significant_index().unwrap_or(self.index),
)
let token = {
kind: Eof,
start: self.offsets[missing_line],
end: self.offsets[missing_line] + 1,
line: missing_line + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Indentation,
"expected indented block".to_string(),
token,
),
)
}
let elif_body = match self.parse_block(elif_indent) {
Ok(value) => value
Err(err) => return Err(err)
}
clauses.push((cond, elif_body))
continue
}
if next_text.has_prefix("else") {
let colon_pos = find_top_level_colon(next_text)
if colon_pos is None {
let token = {
kind: Eof,
start: self.offsets[self.index] + next_indent,
end: self.offsets[self.index] + next_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let raw_body_text = substring(
next_text,
colon_pos.unwrap() + 1,
next_text.length(),
).to_string()
let body_leading = count_indent(raw_body_text)
let body_text = raw_body_text.trim(chars=" \t").to_owned()
self.index += 1
if body_text.length() > 0 {
else_body = []
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 = self.offsets[self.index - 1] +
next_indent +
colon_pos.unwrap() +
1 +
body_leading +
offset
let parsed = match
parse_simple_statement(
segment,
self.index - 1,
self.offsets[self.index - 1],
base_offset,
self.async_depth > 0,
) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in parsed {
else_body.push(stmt)
}
}
} else {
let else_indent = self.peek_next_indent()
if else_indent <= next_indent {
let missing_line = self.clamp_line_index(
self.peek_next_significant_index().unwrap_or(self.index),
)
let token = {
kind: Eof,
start: self.offsets[missing_line],
end: self.offsets[missing_line] + 1,
line: missing_line + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Indentation,
"expected indented block".to_string(),
token,
),
)
}
else_body = match self.parse_block(else_indent) {
Ok(value) => value
Err(err) => return Err(err)
}
}
break
}
break
}
let mut nested_else = else_body
let mut outer : Stmt? = None
let mut i = clauses.length()
while i > 0 {
i -= 1
let clause = clauses[i]
let stmt = Stmt::If(
condition=clause.0,
body=clause.1,
else_body=nested_else,
)
nested_else = [stmt]
outer = Some(stmt)
}
match outer {
Some(stmt) =>
stmts.push(
wrap_stmt_for_line(self.offsets, if_line_index, if_indent, stmt),
)
None => ()
}
continue
}
if line_text.has_prefix("elif ") || line_text.has_prefix("elif(") {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
if line_text.has_prefix("else") {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
if line_text.has_prefix("for ") {
let colon_pos = find_top_level_colon(line_text)
if colon_pos is None {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let mut header_text = substring(line_text, 4, colon_pos.unwrap())
.trim(chars=" \t")
.to_owned()
header_text = strip_wrapping_parens(header_text)
let in_pos = header_text.find(" in ")
if in_pos is None {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let pos = in_pos.unwrap()
let target_text = substring(header_text, 0, pos)
.trim(chars=" \t")
.to_owned()
let iter_text = substring(header_text, pos + 4, header_text.length())
.trim(chars=" \t")
.to_owned()
if target_text.length() == 0 {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let target_tokens = match
tokenize_expr(
target_text,
self.index + 1,
self.offsets[self.index] + current_indent + 4,
current_indent + 5,
) {
Ok(value) => value
Err(err) => return Err(err)
}
let target_token = {
kind: Eof,
start: self.offsets[self.index] + current_indent + 4,
end: self.offsets[self.index] + current_indent + 5,
line: self.index + 1,
column: current_indent + 5,
}
// `for` targets allow starred assignment targets like `for a, *_ in xs:`.
let target = match
target_from_assignment_tokens(target_tokens, target_token) {
Ok(value) => value
Err(err) => return Err(err)
}
let iter_tokens = match
tokenize_expr(
iter_text,
self.index + 1,
self.offsets[self.index] + current_indent + 4,
current_indent + 5,
) {
Ok(value) => value
Err(err) => return Err(err)
}
let iter_expr = match parse_tokens_expr(iter_tokens) {
Ok(value) => value
Err(err) => return Err(err)
}
let raw_body_text = substring(
line_text,
colon_pos.unwrap() + 1,
line_text.length(),
).to_string()
let body_leading = count_indent(raw_body_text)
let body_text = raw_body_text.trim(chars=" \t").to_owned()
self.index = next_index
let mut body : Array[Stmt] = []
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 = self.offsets[self.index - 1] +
current_indent +
colon_pos.unwrap() +
1 +
body_leading +
offset
let parsed = match
parse_simple_statement(
segment,
self.index - 1,
self.offsets[self.index - 1],
base_offset,
self.async_depth > 0,
) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in parsed {
body.push(stmt)
}
}
} else {
let next_indent = self.peek_next_indent()
if next_indent <= current_indent {
let missing_line = self.clamp_line_index(
self.peek_next_significant_index().unwrap_or(self.index),
)
let token = {
kind: Eof,
start: self.offsets[missing_line],
end: self.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 self.parse_block(next_indent) {
Ok(value) => value
Err(err) => return Err(err)
}
}
let else_body : Array[Stmt] = []
if self.index < self.lines.length() {
let else_line = self.lines[self.index]
let else_trimmed = else_line.trim(chars=" \t").to_owned()
let else_indent = count_indent(else_line)
if else_indent == current_indent && else_trimmed.has_prefix("else") {
let colon_pos = find_top_level_colon(else_trimmed)
if colon_pos is None {
let token = {
kind: Eof,
start: self.offsets[self.index] + else_indent,
end: self.offsets[self.index] + else_indent + 1,
line: self.index + 1,
column: else_indent + 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let inline_text = match colon_pos {
Some(pos) =>
strip_inline_comment(
substring(else_trimmed, pos + 1, else_trimmed.length()).to_string(),
)
.trim(chars=" \t")
.to_owned()
None => ""
}
self.index += 1
if inline_text.length() > 0 {
let segments = split_statements(inline_text)
for segment_pair in segments {
let segment = segment_pair.0
let offset = segment_pair.1
if segment.length() == 0 {
continue
}
let parsed = match
parse_simple_statement(
segment,
self.index - 1,
self.offsets[self.index - 1],
self.offsets[self.index - 1] +
else_indent +
colon_pos.unwrap_or(0) +
1 +
offset,
self.async_depth > 0,
) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in parsed {
else_body.push(stmt)
}
}
} else {
let block_indent = self.peek_next_indent()
if block_indent <= current_indent {
let missing_line = self.clamp_line_index(
self.peek_next_significant_index().unwrap_or(self.index),
)
let token = {
kind: Eof,
start: self.offsets[missing_line],
end: self.offsets[missing_line] + 1,
line: missing_line + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Indentation,
"expected indented block".to_string(),
token,
),
)
}
let parsed = match self.parse_block(block_indent) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in parsed {
else_body.push(stmt)
}
}
}
}
stmts.push(
wrap_stmt_for_line(
self.offsets,
line_index,
current_indent,
Stmt::For(target~, iter=iter_expr, body~, else_body~),
),
)
continue
}
if line_text.has_prefix("while ") {
if self.block_depth >= 20 {
return Err(self.invalid_syntax_at_line(self.index + 1))
}
let colon_pos = find_top_level_colon(line_text)
if colon_pos is None {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let mut cond_text = substring(line_text, 5, colon_pos.unwrap())
.trim(chars=" \t")
.to_owned()
cond_text = strip_wrapping_parens(cond_text)
let cond_tokens = match
tokenize_expr(
cond_text,
self.index + 1,
self.offsets[self.index] + current_indent + 5,
current_indent + 6,
) {
Ok(value) => value
Err(err) => return Err(err)
}
let cond_expr = match parse_tokens_expr(cond_tokens) {
Ok(value) => value
Err(err) => return Err(err)
}
let raw_body_text = substring(
line_text,
colon_pos.unwrap() + 1,
line_text.length(),
).to_string()
let body_leading = count_indent(raw_body_text)
let body_text = raw_body_text.trim(chars=" \t").to_owned()
self.index = next_index
let mut body : Array[Stmt] = []
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 = self.offsets[self.index - 1] +
current_indent +
colon_pos.unwrap() +
1 +
body_leading +
offset
let parsed = match
parse_simple_statement(
segment,
self.index - 1,
self.offsets[self.index - 1],
base_offset,
self.async_depth > 0,
) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in parsed {
body.push(stmt)
}
}
} else {
let next_indent = self.peek_next_indent()
if next_indent <= current_indent {
let missing_line = self.clamp_line_index(
self.peek_next_significant_index().unwrap_or(self.index),
)
let token = {
kind: Eof,
start: self.offsets[missing_line],
end: self.offsets[missing_line] + 1,
line: missing_line + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Indentation,
"expected indented block".to_string(),
token,
),
)
}
self.block_depth += 1
let parsed_body = self.parse_block(next_indent)
self.block_depth -= 1
body = match parsed_body {
Ok(value) => value
Err(err) => return Err(err)
}
}
let else_body : Array[Stmt] = []
if self.index < self.lines.length() {
let else_line = self.lines[self.index]
let else_trimmed = else_line.trim(chars=" \t").to_owned()
let else_indent = count_indent(else_line)
if else_indent == current_indent && else_trimmed.has_prefix("else") {
let colon_pos = find_top_level_colon(else_trimmed)
if colon_pos is None {
let token = {
kind: Eof,
start: self.offsets[self.index] + else_indent,
end: self.offsets[self.index] + else_indent + 1,
line: self.index + 1,
column: else_indent + 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let inline_text = match colon_pos {
Some(pos) =>
strip_inline_comment(
substring(else_trimmed, pos + 1, else_trimmed.length()).to_string(),
)
.trim(chars=" \t")
.to_owned()
None => ""
}
self.index += 1
if inline_text.length() > 0 {
let segments = split_statements(inline_text)
for segment_pair in segments {
let segment = segment_pair.0
let offset = segment_pair.1
if segment.length() == 0 {
continue
}
let parsed = match
parse_simple_statement(
segment,
self.index - 1,
self.offsets[self.index - 1],
self.offsets[self.index - 1] +
else_indent +
colon_pos.unwrap_or(0) +
1 +
offset,
self.async_depth > 0,
) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in parsed {
else_body.push(stmt)
}
}
} else {
let block_indent = self.peek_next_indent()
if block_indent <= current_indent {
let missing_line = self.clamp_line_index(
self.peek_next_significant_index().unwrap_or(self.index),
)
let token = {
kind: Eof,
start: self.offsets[missing_line],
end: self.offsets[missing_line] + 1,
line: missing_line + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Indentation,
"expected indented block".to_string(),
token,
),
)
}
let parsed = match self.parse_block(block_indent) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in parsed {
else_body.push(stmt)
}
}
}
}
stmts.push(
wrap_stmt_for_line(
self.offsets,
line_index,
current_indent,
Stmt::While(condition=cond_expr, body~, else_body~),
),
)
continue
}
if line_text == "break" {
stmts.push(
wrap_stmt_for_line(
self.offsets,
line_index,
current_indent,
Stmt::Break,
),
)
self.index = next_index
continue
}
if line_text == "continue" {
stmts.push(
wrap_stmt_for_line(
self.offsets,
line_index,
current_indent,
Stmt::Continue,
),
)
self.index = next_index
continue
}
if has_keyword_prefix(line_text, "try") {
let colon_pos = find_top_level_colon(line_text)
if colon_pos is None ||
substring(line_text, 3, colon_pos.unwrap()).trim(chars=" \t").length() >
0 {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let try_start_index = self.index
let try_body : Array[Stmt] = []
let body_text = strip_inline_comment(
substring(line_text, colon_pos.unwrap() + 1, line_text.length()).to_string(),
)
.trim(chars=" \t")
.to_owned()
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 = self.offsets[self.index] +
current_indent +
colon_pos.unwrap() +
1 +
offset
let parsed = match
parse_simple_statement(
segment,
self.index,
self.offsets[self.index],
base_offset,
self.async_depth > 0,
) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in parsed {
try_body.push(stmt)
}
}
self.index = next_index
} else {
self.index = next_index
let next_indent = self.peek_next_indent()
if next_indent <= current_indent {
let missing_line = self.clamp_line_index(
self.peek_next_significant_index().unwrap_or(self.index),
)
let token = {
kind: Eof,
start: self.offsets[missing_line],
end: self.offsets[missing_line] + 1,
line: missing_line + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Indentation,
"expected indented block".to_string(),
token,
),
)
}
let body = match self.parse_block(next_indent) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in body {
try_body.push(stmt)
}
}
let handlers : Array[ExceptHandler] = []
let else_body : Array[Stmt] = []
let finally_body : Array[Stmt] = []
let mut saw_except = false
let mut saw_star_except = false
let mut saw_plain_except = false
let mut saw_else = false
let mut saw_finally = false
while self.index < self.lines.length() {
let next_line = self.lines[self.index]
let next_trimmed = next_line.trim(chars=" \t").to_owned()
let next_indent = count_indent(next_line)
if next_indent != current_indent {
break
}
if saw_finally {
if next_trimmed.has_prefix("except") ||
next_trimmed.has_prefix("else") ||
next_trimmed.has_prefix("finally") {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
break
}
if saw_else && next_trimmed.has_prefix("except") {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
if next_trimmed.has_prefix("except") {
let header_start_index = self.index
let mut full_header = next_trimmed
let mut header_end_index = self.index + 1
let mut balance = bracket_balance(full_header)
while balance > 0 && header_end_index < self.lines.length() {
let part = self.lines[header_end_index].trim(chars=" \t").to_owned()
full_header = full_header + "\n" + part
header_end_index += 1
balance = bracket_balance(full_header)
}
let next_trimmed = full_header
fn is_space_char(c : Char) -> Bool {
c == ' ' || c == '\t' || c == '\n' || c == '\r'
}
let header_chars = next_trimmed.to_array()
let mut is_star = false
// Offset where the exception expression (if any) begins.
// This must account for `except*`, and also for `except *T` where `*`
// is separated by whitespace/newlines.
let mut header_offset = 6
if next_trimmed.has_prefix("except*") {
is_star = true
header_offset = 7
}
// Also accept `except *TypeError` (PEP 654) where the `*` is separated.
let mut idx = header_offset
while idx < header_chars.length() && is_space_char(header_chars[idx]) {
idx += 1
}
if idx < header_chars.length() && header_chars[idx] == '*' {
is_star = true
idx += 1
while idx < header_chars.length() &&
is_space_char(header_chars[idx]) {
idx += 1
}
header_offset = idx
} else {
header_offset = idx
}
if is_star {
saw_star_except = true
if saw_plain_except {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
} else {
saw_plain_except = true
if saw_star_except {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
}
let colon_pos = find_top_level_colon(next_trimmed)
if colon_pos is None {
let token = {
kind: Eof,
start: self.offsets[header_start_index] + current_indent,
end: self.offsets[header_start_index] + current_indent + 1,
line: header_start_index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
saw_except = true
let header_text = match colon_pos {
Some(pos) =>
substring(next_trimmed, header_offset, pos)
.trim(chars=" \t")
.to_owned()
None => "".to_string()
}
if is_star && header_text.length() == 0 {
let token = {
kind: Eof,
start: self.offsets[header_start_index] + current_indent,
end: self.offsets[header_start_index] + current_indent + 1,
line: header_start_index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
if has_top_level_comma(header_text) &&
!header_text.trim(chars=" \t").to_owned().has_prefix("(") {
let token = {
kind: Eof,
start: self.offsets[header_start_index] + current_indent,
end: self.offsets[header_start_index] + current_indent + 1,
line: header_start_index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let mut exc_expr_opt : Expr? = None
let mut name_opt : String? = None
fn is_simple_except_alias(text : String) -> Bool {
if text.length() == 0 {
return false
}
let chars = text.to_array()
for i = 0; i < chars.length(); i = i + 1 {
let c = chars[i]
if !(is_alnum(c) || c == '_') || (i == 0 && is_digit(c)) {
return false
}
}
true
}
if header_text.length() > 0 {
let parts : Array[String] = []
for part in header_text.split(" as ") {
parts.push(part.to_owned())
}
let expr_text = parts[0].trim(chars=" \t").to_owned()
if expr_text.length() > 0 {
let base_offset = self.offsets[header_start_index] +
current_indent +
header_offset
let tokens = match
tokenize_expr(
expr_text,
header_start_index + 1,
base_offset,
base_offset - self.offsets[header_start_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)
}
exc_expr_opt = Some(expr)
}
if parts.length() > 1 {
let raw_name = parts[1].trim(chars=" \t").to_owned()
if raw_name.length() > 0 {
if !is_simple_except_alias(raw_name) {
return Err(
self.invalid_syntax_at_line(header_start_index + 1),
)
}
name_opt = Some(raw_name)
}
}
}
let inline_text = match colon_pos {
Some(pos) =>
strip_inline_comment(
substring(next_trimmed, pos + 1, next_trimmed.length()).to_string(),
)
.trim(chars=" \t")
.to_owned()
None => ""
}
self.index = header_end_index
let handler_body : Array[Stmt] = []
if inline_text.length() > 0 {
let segments = split_statements(inline_text)
for segment_pair in segments {
let segment = segment_pair.0
let offset = segment_pair.1
if segment.length() == 0 {
continue
}
let base_offset = self.offsets[header_start_index] +
current_indent +
colon_pos.unwrap_or(6) +
1 +
offset
let parsed = match
parse_simple_statement(
segment,
header_start_index,
self.offsets[header_start_index],
base_offset,
self.async_depth > 0,
) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in parsed {
handler_body.push(stmt)
}
}
} else {
let handler_indent = self.peek_next_indent()
if handler_indent <= current_indent {
let missing_line = self.clamp_line_index(
self.peek_next_significant_index().unwrap_or(self.index),
)
let token = {
kind: Eof,
start: self.offsets[missing_line],
end: self.offsets[missing_line] + 1,
line: missing_line + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Indentation,
"expected indented block".to_string(),
token,
),
)
}
let body = match self.parse_block(handler_indent) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in body {
handler_body.push(stmt)
}
}
let start = self.offsets[header_start_index] + current_indent
let mut end = if header_start_index + 1 < self.offsets.length() {
self.offsets[header_start_index + 1] - 1
} else {
start + 1
}
if end <= start {
end = start + 1
}
let header_span = Span::{
start,
end,
line: header_start_index + 1,
column: current_indent + 1,
}
if is_star {
match stmts_first_except_star_control_flow_line(handler_body) {
Some(line) => return Err(self.invalid_syntax_at_line(line))
None => ()
}
}
handlers.push(ExceptHandler::{
span: header_span,
is_star,
exc: exc_expr_opt,
name: name_opt,
body: handler_body,
})
continue
}
if next_trimmed.has_prefix("else") {
let else_header_index = self.index
if !saw_except || saw_else {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let colon_pos = find_top_level_colon(next_trimmed)
if colon_pos is None {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
saw_else = true
let inline_text = match colon_pos {
Some(pos) =>
strip_inline_comment(
substring(next_trimmed, pos + 1, next_trimmed.length()).to_string(),
)
.trim(chars=" \t")
.to_owned()
None => ""
}
self.index += 1
if inline_text.length() > 0 {
let mut suite_text = inline_text
let mut suite_end_index = self.index
let mut balance = bracket_balance(suite_text)
while balance > 0 && suite_end_index < self.lines.length() {
let next_line = self.lines[suite_end_index]
let appended = if find_triple_quote(next_line) is Some(_) {
next_line
} else {
strip_inline_comment(next_line)
}
suite_text = suite_text + "\n" + appended
suite_end_index += 1
balance = bracket_balance(suite_text)
}
self.index = suite_end_index
let segments = split_statements(suite_text)
for segment_pair in segments {
let segment = segment_pair.0
let offset = segment_pair.1
if segment.length() == 0 {
continue
}
let base_offset = self.offsets[else_header_index] +
current_indent +
colon_pos.unwrap_or(4) +
1 +
offset
let parsed = match
parse_simple_statement(
segment,
else_header_index,
self.offsets[else_header_index],
base_offset,
self.async_depth > 0,
) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in parsed {
else_body.push(stmt)
}
}
} else {
let handler_indent = self.peek_next_indent()
if handler_indent <= current_indent {
let missing_line = self.clamp_line_index(
self.peek_next_significant_index().unwrap_or(self.index),
)
let token = {
kind: Eof,
start: self.offsets[missing_line],
end: self.offsets[missing_line] + 1,
line: missing_line + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Indentation,
"expected indented block".to_string(),
token,
),
)
}
let body = match self.parse_block(handler_indent) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in body {
else_body.push(stmt)
}
}
continue
}
if next_trimmed.has_prefix("finally") {
let finally_header_index = self.index
if saw_finally {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let colon_pos = find_top_level_colon(next_trimmed)
if colon_pos is None {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
saw_finally = true
let inline_text = match colon_pos {
Some(pos) =>
strip_inline_comment(
substring(next_trimmed, pos + 1, next_trimmed.length()).to_string(),
)
.trim(chars=" \t")
.to_owned()
None => ""
}
self.index += 1
if inline_text.length() > 0 {
let mut suite_text = inline_text
let mut suite_end_index = self.index
let mut balance = bracket_balance(suite_text)
while balance > 0 && suite_end_index < self.lines.length() {
let next_line = self.lines[suite_end_index]
let appended = if find_triple_quote(next_line) is Some(_) {
next_line
} else {
strip_inline_comment(next_line)
}
suite_text = suite_text + "\n" + appended
suite_end_index += 1
balance = bracket_balance(suite_text)
}
self.index = suite_end_index
let segments = split_statements(suite_text)
for segment_pair in segments {
let segment = segment_pair.0
let offset = segment_pair.1
if segment.length() == 0 {
continue
}
let base_offset = self.offsets[finally_header_index] +
current_indent +
colon_pos.unwrap_or(7) +
1 +
offset
let parsed = match
parse_simple_statement(
segment,
finally_header_index,
self.offsets[finally_header_index],
base_offset,
self.async_depth > 0,
) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in parsed {
finally_body.push(stmt)
}
}
} else {
let handler_indent = self.peek_next_indent()
if handler_indent <= current_indent {
let missing_line = self.clamp_line_index(
self.peek_next_significant_index().unwrap_or(self.index),
)
let token = {
kind: Eof,
start: self.offsets[missing_line],
end: self.offsets[missing_line] + 1,
line: missing_line + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Indentation,
"expected indented block".to_string(),
token,
),
)
}
let body = match self.parse_block(handler_indent) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in body {
finally_body.push(stmt)
}
}
continue
}
break
}
if handlers.length() == 0 && finally_body.length() == 0 {
let token = {
kind: Eof,
start: self.offsets[try_start_index] + current_indent,
end: self.offsets[try_start_index] + current_indent + 1,
line: try_start_index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
stmts.push(
wrap_stmt_for_line(
self.offsets,
try_start_index,
current_indent,
Stmt::Try(body=try_body, handlers~, else_body~, finally_body~),
),
)
continue
}
if line_text.has_prefix("with ") || line_text.has_prefix("with(") {
let mut full_line_text = line_text
let mut colon_pos = find_top_level_colon(full_line_text)
let mut header_end_index = next_index
if colon_pos is None {
let mut scan = next_index
while scan < self.lines.length() {
let part = self.lines[scan].trim(chars=" \t").to_owned()
full_line_text = full_line_text + "\n" + part
colon_pos = find_top_level_colon(full_line_text)
scan += 1
if colon_pos is Some(_) {
header_end_index = scan
break
}
}
}
if colon_pos is None {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let header_text = substring(full_line_text, 4, colon_pos.unwrap())
.trim(chars=" \t")
.to_owned()
let mut item_text = header_text
match find_top_level_paren_span(item_text) {
Some(span) =>
if span.0 == 0 && span.1 == item_text.length() - 1 {
item_text = substring(item_text, 1, item_text.length() - 1).to_string()
}
None => ()
}
let with_items : Array[(Expr, String?)] = []
for raw_item in split_top_level_commas(item_text) {
let item = raw_item.trim(chars=" \t\n\r").to_owned()
if item.length() == 0 {
// Allow trailing commas inside `with (...)`.
continue
}
let mut context_text = item
let mut target : String? = None
match find_top_level_as_pos(item) {
Some(pos) => {
context_text = substring(item, 0, pos)
.trim(chars=" \t\n\r")
.to_owned()
let mut name_text = substring(item, pos + 2, item.length())
.trim(chars=" \t\n\r")
.to_owned()
let comma_pos = find_char(name_text, ',')
if comma_pos is Some(pos) {
name_text = substring(name_text, 0, pos)
.trim(chars=" \t")
.to_owned()
}
let close_pos = find_char(name_text, ')')
if close_pos is Some(pos) {
name_text = substring(name_text, 0, pos)
.trim(chars=" \t")
.to_owned()
}
if name_text.length() > 0 {
target = Some(name_text)
}
}
None => ()
}
let context_tokens = match
tokenize_expr(
context_text,
self.index + 1,
self.offsets[self.index] + current_indent + 4,
current_indent + 5,
) {
Ok(value) => value
Err(err) => return Err(err)
}
let context = match parse_tokens_expr(context_tokens) {
Ok(value) => value
Err(err) => return Err(err)
}
with_items.push((context, target))
}
if with_items.length() == 0 {
let token = {
kind: Eof,
start: self.offsets[self.index] + current_indent,
end: self.offsets[self.index] + current_indent + 1,
line: self.index + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
token,
),
)
}
let raw_body_text = substring(
full_line_text,
colon_pos.unwrap() + 1,
full_line_text.length(),
).to_string()
let body_leading = count_indent(raw_body_text)
let body_text = raw_body_text.trim(chars=" \t").to_owned()
if body_text.length() > 0 {
let body : Array[Stmt] = []
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 = self.offsets[self.index] +
current_indent +
colon_pos.unwrap() +
1 +
body_leading +
offset
let parsed = match
parse_simple_statement(
segment,
self.index,
self.offsets[self.index],
base_offset,
self.async_depth > 0,
) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in parsed {
body.push(stmt)
}
}
let mut nested_body = body
for i = with_items.length() - 1; i >= 0; i = i - 1 {
let (context, target) = with_items[i]
nested_body = [
wrap_stmt_for_line(
self.offsets,
line_index,
current_indent,
Stmt::With(context~, target~, body=nested_body),
),
]
}
stmts.push(nested_body[0])
self.index = header_end_index
continue
}
self.index = next_index
let next_indent = self.peek_next_indent()
if next_indent <= current_indent {
let missing_line = self.clamp_line_index(
self.peek_next_significant_index().unwrap_or(self.index),
)
let token = {
kind: Eof,
start: self.offsets[missing_line],
end: self.offsets[missing_line] + 1,
line: missing_line + 1,
column: 1,
}
return Err(
make_parse_error(
ParseErrorKind::Indentation,
"expected indented block".to_string(),
token,
),
)
}
let body = match self.parse_block(next_indent) {
Ok(value) => value
Err(err) => return Err(err)
}
let mut nested_body = body
for i = with_items.length() - 1; i >= 0; i = i - 1 {
let (context, target) = with_items[i]
nested_body = [
wrap_stmt_for_line(
self.offsets,
line_index,
current_indent,
Stmt::With(context~, target~, body=nested_body),
),
]
}
stmts.push(nested_body[0])
continue
}
let segments = split_statements(line_text)
for segment_pair in segments {
let segment = segment_pair.0
let offset = segment_pair.1
if segment.length() == 0 {
continue
}
let base_offset = self.offsets[self.index] + current_indent + offset
let parsed = match
parse_simple_statement(
segment,
self.index,
self.offsets[self.index],
base_offset,
self.async_depth > 0,
) {
Ok(value) => value
Err(err) => return Err(err)
}
for stmt in parsed {
stmts.push(stmt)
}
}
self.index = next_index
}
if pending_decorators.length() > 0 {
return Err(
make_parse_error(
ParseErrorKind::Syntax,
"invalid syntax".to_string(),
pending_decorator_token.unwrap(),
),
)
}
if self.async_depth == 0 && indent == 0 {
match stmts_first_illegal_await_line(stmts) {
Some(line) => return Err(self.invalid_syntax_at_line(line))
None => ()
}
}
Ok(stmts)
}