Skip to content

Commit af84760

Browse files
authored
feat: fuel (#23)
Fuel is consumed when you parse an expression, and refilled when you increment the token. When the parser runs out of fuel, there is most likely a bug and it will raise with a NoFuelRemaining exception
1 parent 4a2fcb6 commit af84760

File tree

1 file changed

+33
-5
lines changed

1 file changed

+33
-5
lines changed

lib/spitfire.ex

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ defmodule Spitfire do
55

66
require Logger
77

8+
defmodule NoFuelRemaining do
9+
@moduledoc false
10+
defexception message: """
11+
The parser ran out of fuel!
12+
13+
This happens when the parser recurses too many times without consuming a new token,
14+
and most likely indicates a bug in the parser.
15+
"""
16+
end
17+
818
# precedences
919

1020
# pratt parsers are top down operator precedence recursive descent parsers
@@ -194,6 +204,8 @@ defmodule Spitfire do
194204
defp parse_expression(parser, assoc \\ @lowest, is_list \\ false, is_map \\ false, is_top \\ false, is_stab \\ false)
195205

196206
defp parse_expression(parser, {associativity, precedence}, is_list, is_map, is_top, is_stab) do
207+
parser = consume_fuel(parser)
208+
197209
prefix =
198210
case current_token_type(parser) do
199211
:identifier -> &parse_identifier/1
@@ -1214,6 +1226,7 @@ defmodule Spitfire do
12141226
current_token: nil,
12151227
peek_token: nil,
12161228
nesting: 0,
1229+
fuel: 150,
12171230
errors: [],
12181231
literal_encoder: parser.literal_encoder
12191232
}
@@ -1281,6 +1294,7 @@ defmodule Spitfire do
12811294
current_token: nil,
12821295
peek_token: nil,
12831296
nesting: 0,
1297+
fuel: 150,
12841298
errors: [],
12851299
literal_encoder: parser.literal_encoder
12861300
}
@@ -1977,6 +1991,7 @@ defmodule Spitfire do
19771991
current_token: nil,
19781992
peek_token: nil,
19791993
nesting: 0,
1994+
fuel: 150,
19801995
literal_encoder: parser.literal_encoder
19811996
}
19821997
|> next_token()
@@ -2003,6 +2018,7 @@ defmodule Spitfire do
20032018
defp new(code, opts) do
20042019
%{
20052020
tokens: tokenize(code, opts),
2021+
fuel: 150,
20062022
current_token: nil,
20072023
peek_token: nil,
20082024
nesting: 0,
@@ -2016,23 +2032,24 @@ defmodule Spitfire do
20162032
end
20172033

20182034
defp next_token(%{tokens: :eot, current_token: :eof, peek_token: nil} = parser) do
2019-
%{parser | tokens: :eot, current_token: nil}
2035+
%{parser | tokens: :eot, current_token: nil, fuel: 150}
20202036
end
20212037

20222038
defp next_token(%{tokens: [], current_token: nil, peek_token: nil} = parser) do
2023-
%{parser | tokens: :eot}
2039+
%{parser | tokens: :eot, fuel: 150}
20242040
end
20252041

20262042
defp next_token(%{tokens: [], peek_token: nil} = parser) do
2027-
%{parser | tokens: :eot, current_token: nil}
2043+
%{parser | tokens: :eot, current_token: nil, fuel: 150}
20282044
end
20292045

20302046
defp next_token(%{tokens: []} = parser) do
20312047
%{
20322048
parser
20332049
| current_token: parser.peek_token,
20342050
peek_token: nil,
2035-
tokens: :eot
2051+
tokens: :eot,
2052+
fuel: 150
20362053
}
20372054
end
20382055

@@ -2041,10 +2058,21 @@ defmodule Spitfire do
20412058
parser
20422059
| tokens: tokens,
20432060
current_token: parser.peek_token,
2044-
peek_token: token
2061+
peek_token: token,
2062+
fuel: 150
20452063
}
20462064
end
20472065

2066+
defp consume_fuel(parser) do
2067+
parser = Map.update!(parser, :fuel, &(&1 - 1))
2068+
2069+
if parser.fuel < 1 do
2070+
raise Spitfire.NoFuelRemaining
2071+
end
2072+
2073+
parser
2074+
end
2075+
20482076
defp eat(edible, %{tokens: [], current_token: {edible, _}, peek_token: nil} = parser) do
20492077
%{
20502078
parser

0 commit comments

Comments
 (0)