Skip to content

Commit cf5631c

Browse files
committed
Add safe navigator
It behaves like Ruby's safe navigator (&.). Importantly, it does not short-circuit like JavaScript's optional chaining (?.). Closes #34
1 parent 73de285 commit cf5631c

File tree

8 files changed

+34
-4
lines changed

8 files changed

+34
-4
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
let a = nil
2+
a&.b.c # error: Runtime error: Only instances have properties, got Nil.

spec/e2e/safe_navigation/valid.lit

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
type A {
2+
fn b do "b"
3+
fn b_nil do nil
4+
fn b_c do A()
5+
fn c do "c"
6+
}
7+
8+
let a = nil
9+
debug(a&.b) # expect: nil
10+
debug(a&.b&.c) # expect: nil
11+
12+
let a_instance = A()
13+
debug(a_instance&.b()) # expect: "b"
14+
debug(a_instance&.b_c()&.c()) # expect: "c"
15+
16+
debug(a_instance&.b_nil()) # expect: nil
17+
debug(a_instance&.b_nil()&.unknown) # expect: nil
18+
debug(a_instance.b_nil()&.unknown) # expect: nil

spec/lit/scanner_spec.cr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ describe Lit::Scanner do
4444
it_scans "<=", to_type: token_type(LESS_EQUAL)
4545
it_scans "|", to_type: token_type(BAR)
4646
it_scans "||", to_type: token_type(OR)
47+
it_scans "&.", to_type: token_type(AMPERSAND_DOT)
4748
it_scans "&&", to_type: token_type(AND)
4849
it_scans "|>", to_type: token_type(PIPE_GREATER)
4950
it_scans "!=", to_type: token_type(BANG_EQUAL)

src/lit/expr.cr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ module Lit
151151

152152
class If < Expr
153153
getter condition : Expr
154-
getter then_branch : Expr::Block
155-
getter else_branch : Expr::Block?
154+
getter then_branch : Expr
155+
getter else_branch : Expr?
156156

157157
def initialize(@condition, @then_branch, @else_branch); end
158158

src/lit/parser.cr

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,13 @@ module Lit
433433
ignore_newlines
434434
name = consume(TokenType::IDENTIFIER, "I was expecting a property name after '.'.")
435435
expr = Expr::Get.new(expr, name)
436+
elsif match?(TokenType::AMPERSAND_DOT)
437+
token = previous
438+
ignore_newlines
439+
name = consume(TokenType::IDENTIFIER, "I was expecting a property name after '&.'.")
440+
# # Desugar a&.b into: if a != nil then a.b else nil
441+
condition = Expr::Binary.new(expr, token.with_type(TokenType::BANG_EQUAL), Expr::Literal.new(nil))
442+
expr = Expr::If.new(condition, then_branch: Expr::Get.new(expr, name), else_branch: Expr::Literal.new(nil))
436443
else
437444
break
438445
end

src/lit/scanner.cr

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ module Lit
119119
when '&'
120120
if match?('&')
121121
add_token(TokenType::AND)
122+
elsif match?('.')
123+
add_token(TokenType::AMPERSAND_DOT)
122124
else
123125
syntax_error("Unexpected character #{c.inspect}")
124126
end

src/lit/token_type.cr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module Lit
22
enum TokenType
33
LEFT_PAREN; RIGHT_PAREN; LEFT_BRACKET; RIGHT_BRACKET; LEFT_BRACE; RIGHT_BRACE
44

5-
COMMA; DOT; COLON; NEWLINE
5+
COMMA; DOT; COLON; NEWLINE; AMPERSAND_DOT
66

77
# Math
88
PLUS; MINUS; SLASH; STAR; STAR_STAR; PERCENT

tools/generate_ast

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ module AstGenerator
1818
"Set = object : Expr, name : Token, value : Expr",
1919
"Grouping = expression : Expr",
2020
"Literal = value : Union(String | Int64 | Float64 | Bool | Nil)",
21-
"If = condition : Expr, then_branch : Expr::Block, else_branch : Expr::Block?",
21+
"If = condition : Expr, then_branch : Expr, else_branch : Expr?",
2222
"Import = path : Token",
2323
"Logical = left : Expr, operator : Token, right : Expr",
2424
"Self = keyword : Token",

0 commit comments

Comments
 (0)