Skip to content

Commit

Permalink
compiler: Add handling of with statements, tweak other things
Browse files Browse the repository at this point in the history
DCO-1.1-Signed-off-by: Ellie <[email protected]>
  • Loading branch information
ell1e committed Oct 15, 2023
1 parent 6d3c2de commit 893a73a
Show file tree
Hide file tree
Showing 4 changed files with 254 additions and 12 deletions.
3 changes: 3 additions & 0 deletions src/compiler/ast/ast.h64
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import compiler.ast.import_stmt as import_stmt
import compiler.ast.invalid_stmt as invalid_stmt
import compiler.ast.return_stmt as return_stmt
import compiler.ast.var_stmt as var_stmt
import compiler.ast.with_stmt as with_stmt
import compiler.msg as msg
import compiler.token as token

Expand Down Expand Up @@ -139,6 +140,7 @@ enum NodeKind {
N_EXPR_IFELSECLAUSE,
N_STMT_AWAIT,
N_STMT_FOR,
N_STMT_WITH,
N_STMT_FUNC,
N_STMT_CALL,
N_STMT_ASSIGN,
Expand Down Expand Up @@ -190,6 +192,7 @@ func ASTResult.as_json_obj {
var stmt_parse_funcs = [
call_or_assign_stmt.parse,
await_stmt.parse,
with_stmt.parse,
for_stmt.parse,
if_stmt.parse,
func_stmt.parse,
Expand Down
25 changes: 14 additions & 11 deletions src/compiler/ast/block.h64
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func CodeBlockResult.has_damage {
}

func parse(tokens, pos, msgs, project_file=none, debug=no,
block_purpose_name="") {
had_damage=no, block_purpose_name="") {
var startpos = pos
const tokens_len = tokens.len
var result = new CodeBlockResult()
Expand All @@ -63,21 +63,24 @@ func parse(tokens, pos, msgs, project_file=none, debug=no,
if pos > tokens_len or
tokens[pos].kind != token.T_BRACKET or
tokens[pos].str != "{" {
msgs.add(new msg.FileMsg(
"Unexpected " + token.describe_token_at(tokens, pos) +
", expected '{' (T_BRACKET) to start "
"code block" +
if block_purpose_name.len > 0 (" ") else ("") +
block_purpose_name + ".",
source_file=project_file,
line=token.get_line(tokens, pos),
col=token.get_col(tokens, pos),
))
if not had_damage {
msgs.add(new msg.FileMsg(
"Unexpected " + token.describe_token_at(tokens, pos) +
", expected '{' (T_BRACKET) to start "
"code block" +
if block_purpose_name.len > 0 (" ") else ("") +
block_purpose_name + ".",
source_file=project_file,
line=token.get_line(tokens, pos),
col=token.get_col(tokens, pos),
))
}
pos -= 1
var skiplen = token.get_naive_stmt_or_expr_len(
tokens, pos, bracket_depth=0, for_expr=no
)
assert(skiplen > 0)
result.damaged = yes
result.token_len = (pos - startpos) + skiplen
return result
}
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/ast/for_stmt.h64
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ func parse(tokens, pos, msgs, project_file=none, debug=no) {
# Parse inner code block:
var block_result = block.parse(
tokens, pos, msgs, project_file=project_file,
debug=debug,
debug=debug, had_damage=stmt.has_damage(),
block_purpose_name="for the for loop statement starting in "
"line " + token.get_line(tokens, startpos).as_str() +
", column " + token.get_col(tokens, startpos).as_str()
Expand Down
236 changes: 236 additions & 0 deletions src/compiler/ast/with_stmt.h64
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
# @module compiler.ast.with_stmt
# Copyright (c) 2023, ellie/@ell1e & Horse64 Team (see AUTHORS.md).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# Alternatively, at your option, this file is offered under the Apache 2
# license, see accompanied LICENSE.md.

import math from core.horse64.org

import compiler.ast as ast
import compiler.ast.block as block
import compiler.ast.expr as expr
import compiler.ast.invalid_stmt as invalid_stmt
import compiler.msg as msg
import compiler.token as token

type WithStmt base ast.StmtNode {
}

func WithStmt.init {
base.init()
self.kind = ast.N_STMT_WITH
}

func WithStmt.as_json_obj {
var output = base.as_json_obj()
return output
}

func parse(tokens, pos, msgs, project_file=none, debug=no) {
var stmt = new WithStmt()
var tokens_len = tokens.len
var startpos = pos

if pos > tokens_len or
tokens[pos].kind != token.T_KEYWORD or
tokens[pos].str != "with" {
return later none
}

if debug {
print("compiler.ast.with_stmt: parse() on " +
tokens.sub(pos, math.min(pos + 10, tokens_len)).as_str() +
"... with pos=" + pos.as_str() + ", "
"tokens.len=" + tokens.len.as_str())
}
stmt.pos = token.get_line(tokens, pos)
stmt.col = token.get_col(tokens, pos)
pos += 1

# See how long our with statement expression is:
var with_expr_start = pos
var bracket_depth = 0
while pos <= tokens_len and (bracket_depth > 0 or (
(tokens[pos].kind != token.T_KEYWORD or
tokens[pos].str != "as") and
(tokens[pos].kind != token.T_BRACKET or
tokens[pos].str != "{" or
pos <= startpos + 1 or
token.token_has_righthand(tokens[pos])) and
not token.surely_starts_stmt_even_in_bad_code(
tokens, pos
))) and
not token.surely_to_toplevel_even_in_bad_code(
tokens, pos
) {
if tokens[pos].kind == token.T_BRACKET {
if {"(", "[", "{"}.has(tokens[pos].str) {
bracket_depth += 1
} else {
assert({")", "]", "}"}.has(tokens[pos].str))
bracket_depth -= 1
}
}
}
if pos >= tokens_len or (
(tokens[pos].kind != token.T_KEYWORD or
tokens[pos].str != "as") and
(tokens[pos].kind != token.T_BRACKET or
tokens[pos].str != "{")) {
msgs.add(new msg.FileMsg(
"Unexpected " + token.describe_token_at(tokens,
with_expr_start) +
", expected valid expression for with "
"statement.",
source_file=project_file,
line=token.get_line(tokens, with_expr_start),
col=token.get_col(tokens, with_expr_start),
))
stmt.damaged = yes
}

# Try to parse with statement expression:
var with_expr = expr.parse_expression(
tokens, pos, msgs,
project_file=project_file,
max_len=pos
) later:

await with_expr
if with_expr == none {
if not stmt.has_damage() {
msgs.add(new msg.FileMsg(
"Unexpected " + token.describe_token_at(
tokens, with_expr_start) +
", expected valid expression for with "
"statement.",
source_file=project_file,
line=token.get_line(tokens, with_expr_start),
col=token.get_col(tokens, with_expr_start),
))
}
stmt.damaged = yes
with_expr = new expr.InvalidExpr(math.max(1,
pos - (startpos + 1)
))
}

# Read the 'as' label:
var as_label = none
if pos > tokens_len or
tokens[pos].kind != token.T_KEYWORD or
tokens[pos].str != "as" {
if not stmt.has_damage() {
msgs.add(new msg.FileMsg(
"Unexpected " + token.describe_token_at(
tokens, pos) +
", expected 'as' keyword for with "
"statement starting in line " +
token.get_line(tokens, startpos).as_str() +
", column " +
token.get_col(tokens, startpos) + ".",
source_file=project_file,
line=token.get_line(tokens, pos),
col=token.get_col(tokens, pos),
))
}
stmt.damaged = yes
} else {
pos += 1
if pos > tokens_len or
tokens[pos].kind != token.T_IDENT {
if not stmt.has_damage() {
msgs.add(new msg.FileMsg(
"Unexpected " + token.describe_token_at(
tokens, pos) +
", expected identifier after 'as' keyword.",
source_file=project_file,
line=token.get_line(tokens, pos),
col=token.get_col(tokens, pos),
))
}
stmt.damaged = yes
}
as_label = tokens[pos].str
pos += 1
}

# Advance to block (not needed if the code was correct):
if pos < tokens_len and
(tokens[pos].kind != token.T_BRACKET or
tokens[pos].str != "{") {
var beforeskippos = pos
var skiplen = token.get_naive_stmt_or_expr_len(
tokens, pos - 1, bracket_depth=0, for_expr=no
)
var skipto = pos + skiplen
while pos < skipto and (
tokens[pos].kind != token.T_BRACKET or
tokens[pos].str != "{") {
pos += 1
}
if not stmt.has_damage() {
msgs.add(new msg.FileMsg(
"Unexpected " + token.describe_token_at(tokens,
beforeskippos) +
", expected '{' to open code block.",
source_file=project_file,
line=token.get_line(tokens, beforeskippos),
col=token.get_col(tokens, beforeskippos),
))
}
stmt.damaged = yes
}

# Put together the collected info of the with statement:
var iexp = none
if as_label == none {
iexp = new expr.InvalidExpr(1)
} else {
iesp = new expr.IdRefExpr()
iexp.label = as_label
}
stmt.subexprs.add(iexp)
stmt.subexprs.add(with_expr)

# Parse inner code block:
var block_result = block.parse(
tokens, pos, msgs, project_file=project_file,
debug=debug, had_damage=stmt.has_damage(),
block_purpose_name="for the with statement starting in "
"line " + token.get_line(tokens, startpos).as_str() +
", column " + token.get_col(tokens, startpos).as_str()
) later:

await block_result
if block_result.has_damage() {
stmt.damaged = yes
}
pos += block_result.token_len
stmt.subblocks.add(block_result.stmts)

stmt.token_len = (pos - startpos)
return later stmt
}

0 comments on commit 893a73a

Please sign in to comment.