Skip to content

Commit 4a0262f

Browse files
committed
Implement assert statement
1 parent 363af38 commit 4a0262f

File tree

9 files changed

+108
-1
lines changed

9 files changed

+108
-1
lines changed

src/compiler/crystal/semantic/ast.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,7 @@ module Crystal
538538
ArrayLiteral HashLiteral RegexLiteral RangeLiteral
539539
Case StringInterpolation
540540
MacroExpression MacroIf MacroFor MultiAssign
541-
SizeOf InstanceSizeOf Global Require Select) %}
541+
SizeOf InstanceSizeOf Global Require Select Assert) %}
542542
class {{name.id}}
543543
include ExpandableNode
544544
end

src/compiler/crystal/semantic/literal_expander.cr

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -528,6 +528,48 @@ module Crystal
528528
end
529529
end
530530

531+
def expand(node : Assert)
532+
transformer = AssertExprTransformer.new(@program)
533+
(expr = node.expression).transform(visitor)
534+
535+
string = [StringLiteral.new(source.inspect).at(node)] of ASTNode
536+
537+
transformer.items.each do |assign|
538+
location = case (val = assign.value)
539+
when Crystal::Call
540+
val.name_column_number
541+
else
542+
val.location.column_number
543+
end - 1
544+
545+
string << StringLiteral.new(" " * location + "^").at(node) << assign.value
546+
end
547+
548+
Unless.new(expr,
549+
Call.new(nil, "raise",
550+
Call.new(Path.global("AssertionFailed"), "new",
551+
StringInterpolation.new(string).at(node)
552+
).at(node)
553+
).at(node)
554+
).at(node)
555+
end
556+
557+
class AssertExprTransformer < Transformer
558+
getter items = [] of Assign
559+
560+
def initialize(@program : Program)
561+
end
562+
563+
def transform(node : ASTNode)
564+
unless node.class.name.ends_with? "Literal"
565+
if node.location
566+
items << (node = Assign.new(Var.new(@program.new_temp_var), node))
567+
end
568+
end
569+
node
570+
end
571+
end
572+
531573
# Transform a multi assign into many assigns.
532574
def expand(node : MultiAssign)
533575
# From:

src/compiler/crystal/semantic/main_visitor.cr

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2662,6 +2662,11 @@ module Crystal
26622662
false
26632663
end
26642664

2665+
def visit(node : Assert)
2666+
node.expression.accept self
2667+
false
2668+
end
2669+
26652670
def visit(node : MultiAssign)
26662671
expand(node)
26672672
false

src/compiler/crystal/syntax/ast.cr

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,6 +1150,24 @@ module Crystal
11501150
def_equals_and_hash @whens, @else
11511151
end
11521152

1153+
class Assert < ASTNode
1154+
property expression : ASTNode
1155+
property source : String
1156+
1157+
def initialize(@expression, @source)
1158+
end
1159+
1160+
def accept_children(visitor)
1161+
expression.accept visitor
1162+
end
1163+
1164+
def clone_without_location
1165+
Assert.new(@expression, @source)
1166+
end
1167+
1168+
def_equals_and_hash @expression
1169+
end
1170+
11531171
# Node that represents an implicit obj in:
11541172
#
11551173
# case foo

src/compiler/crystal/syntax/lexer.cr

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,11 @@ module Crystal
700700
when 'm'
701701
next_char
702702
return check_ident_or_keyword(:asm, start)
703+
when 's'
704+
next_char
705+
if next_char == 'e' && next_char == 'r' && next_char == 't'
706+
return check_ident_or_keyword(:assert, start)
707+
end
703708
when '?'
704709
next_char
705710
next_char

src/compiler/crystal/syntax/parser.cr

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,6 +1032,8 @@ module Crystal
10321032
check_type_declaration { parse_case }
10331033
when :select
10341034
check_type_declaration { parse_select }
1035+
when :assert
1036+
check_type_declaration { parse_assert }
10351037
when :if
10361038
check_type_declaration { parse_if }
10371039
when :ifdef
@@ -2555,6 +2557,17 @@ module Crystal
25552557
end
25562558
end
25572559

2560+
def parse_assert
2561+
location = @token.location
2562+
2563+
next_token_skip_space
2564+
2565+
pos = reader.pos
2566+
expr = parse_expression
2567+
source = reader.string[pos...@reader.pos]
2568+
Assert.new(expr, source).at(location)
2569+
end
2570+
25582571
def parse_include
25592572
parse_include_or_extend Include
25602573
end

src/compiler/crystal/syntax/to_s.cr

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,6 +1282,13 @@ module Crystal
12821282
false
12831283
end
12841284

1285+
def visit(node : Assert)
1286+
@str << keyword("assert")
1287+
@str << " "
1288+
node.expression.accept self
1289+
false
1290+
end
1291+
12851292
def visit(node : ImplicitObj)
12861293
false
12871294
end

src/compiler/crystal/syntax/transformer.cr

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,16 @@ module Crystal
232232
node
233233
end
234234

235+
def transform(node : Assert)
236+
new_expr = node.expression.transform(self)
237+
unless new_expr == node.expression
238+
node.expression = new_expr
239+
node.source = new_expr.to_s
240+
end
241+
242+
node
243+
end
244+
235245
def transform(node : ImplicitObj)
236246
node
237247
end

src/compiler/crystal/tools/formatter.cr

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3328,6 +3328,13 @@ module Crystal
33283328
false
33293329
end
33303330

3331+
def visit(node : Assert)
3332+
write_keyword :assert, " "
3333+
accept node.expression
3334+
3335+
false
3336+
end
3337+
33313338
def visit(node : Attribute)
33323339
write_token :"@["
33333340
skip_space_or_newline

0 commit comments

Comments
 (0)