@@ -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