@@ -220,6 +220,7 @@ defmodule NimbleParsecTest do
220220 defparsecp :min_ascii_string , ascii_string ( [ ?0 .. ?9 ] , min: 2 )
221221 defparsecp :max_ascii_string , ascii_string ( [ ?0 .. ?9 ] , max: 3 )
222222 defparsecp :min_max_ascii_string , ascii_string ( [ ?0 .. ?9 ] , min: 2 , max: 3 )
223+ defparsecp :min_zero_ascii_string , ascii_string ( [ ?0 .. ?9 ] , min: 0 )
223224
224225 @ error "expected ASCII character in the range \" 0\" to \" 9\" , followed by ASCII character in the range \" 0\" to \" 9\" "
225226
@@ -229,6 +230,11 @@ defmodule NimbleParsecTest do
229230 assert min_ascii_string ( "123o" ) == { :ok , [ "123" ] , "o" , % { } , { 1 , 0 } , 3 }
230231 assert min_ascii_string ( "1234" ) == { :ok , [ "1234" ] , "" , % { } , { 1 , 0 } , 4 }
231232 assert min_ascii_string ( "1" ) == { :error , @ error , "1" , % { } , { 1 , 0 } , 0 }
233+
234+ assert min_zero_ascii_string ( "12" ) == { :ok , [ "12" ] , "" , % { } , { 1 , 0 } , 2 }
235+ assert min_zero_ascii_string ( "123o" ) == { :ok , [ "123" ] , "o" , % { } , { 1 , 0 } , 3 }
236+ assert min_zero_ascii_string ( "o" ) == { :ok , [ "" ] , "o" , % { } , { 1 , 0 } , 0 }
237+ assert min_zero_ascii_string ( "" ) == { :ok , [ "" ] , "" , % { } , { 1 , 0 } , 0 }
232238 end
233239
234240 test "returns ok/error with max" do
@@ -247,6 +253,10 @@ defmodule NimbleParsecTest do
247253 assert min_max_ascii_string ( "12o" ) == { :ok , [ "12" ] , "o" , % { } , { 1 , 0 } , 2 }
248254 end
249255
256+ test "treats a max without a min as min: 0" do
257+ assert ascii_string ( [ ?0 .. ?9 ] , max: 3 ) == ascii_string ( [ ?0 .. ?9 ] , min: 0 , max: 3 )
258+ end
259+
250260 test "is not bound" do
251261 assert not_bound? ( ascii_string ( [ ?0 .. ?9 ] , min: 3 ) )
252262 assert not_bound? ( ascii_string ( [ ?0 .. ?9 ] , max: 3 ) )
@@ -309,6 +319,10 @@ defmodule NimbleParsecTest do
309319 assert min_max_utf8_string ( "áé\xFF " ) == { :ok , [ "áé" ] , "\xFF " , % { } , { 1 , 0 } , 4 }
310320 end
311321
322+ test "treats a max without a min as min: 0" do
323+ assert utf8_string ( [ ] , max: 3 ) == utf8_string ( [ ] , min: 0 , max: 3 )
324+ end
325+
312326 test "is not bound" do
313327 assert not_bound? ( utf8_string ( [ ] , min: 3 ) )
314328 assert not_bound? ( utf8_string ( [ ] , max: 3 ) )
@@ -320,6 +334,7 @@ defmodule NimbleParsecTest do
320334 defparsecp :min_sliced_utf8_string , utf8_string ( [ ?a .. ?z , ?á .. ?é ] , min: 2 )
321335 defparsecp :max_sliced_utf8_string , utf8_string ( [ ?a .. ?z , ?á .. ?é ] , max: 3 )
322336 defparsecp :min_max_sliced_utf8_string , utf8_string ( [ ?a .. ?z , ?á .. ?é ] , min: 2 , max: 3 )
337+ defparsecp :min_zero_sliced_utf8_string , utf8_string ( [ ?a .. ?z , ?á .. ?é ] , min: 0 )
323338 defparsecp :not_newline_utf8_string , utf8_string ( [ not: ?\n ] , min: 1 )
324339
325340 @ error "expected utf8 codepoint in the range \" a\" to \" z\" or in the range \" á\" to \" é\" , " <>
@@ -331,6 +346,11 @@ defmodule NimbleParsecTest do
331346 assert min_sliced_utf8_string ( "áé1" ) == { :ok , [ "áé" ] , "1" , % { } , { 1 , 0 } , 4 }
332347 assert min_sliced_utf8_string ( "á" ) == { :error , @ error , "á" , % { } , { 1 , 0 } , 0 }
333348 assert min_sliced_utf8_string ( "1á" ) == { :error , @ error , "1á" , % { } , { 1 , 0 } , 0 }
349+
350+ assert min_zero_sliced_utf8_string ( "áé" ) == { :ok , [ "áé" ] , "" , % { } , { 1 , 0 } , 4 }
351+ assert min_zero_sliced_utf8_string ( "aébc" ) == { :ok , [ "aébc" ] , "" , % { } , { 1 , 0 } , 5 }
352+ assert min_zero_sliced_utf8_string ( "1á" ) == { :ok , [ "" ] , "1á" , % { } , { 1 , 0 } , 0 }
353+ assert min_zero_sliced_utf8_string ( "" ) == { :ok , [ "" ] , "" , % { } , { 1 , 0 } , 0 }
334354 end
335355
336356 test "returns ok/error with max" do
@@ -351,6 +371,46 @@ defmodule NimbleParsecTest do
351371 end
352372 end
353373
374+ describe "ascii_string/3 over a newline-free range nested in other combinators" do
375+ defparsecp :lookahead_sliced ,
376+ lookahead ( ascii_string ( [ ?a .. ?z ] , min: 2 ) ) |> concat ( string ( "ab" ) )
377+
378+ defparsecp :lookahead_not_sliced ,
379+ lookahead_not ( ascii_string ( [ ?a .. ?z ] , min: 2 ) ) |> concat ( string ( "12" ) )
380+
381+ defparsecp :choice_sliced ,
382+ choice ( [ ascii_string ( [ ?a .. ?z ] , min: 2 ) , ascii_string ( [ ?0 .. ?9 ] , min: 2 ) ] )
383+
384+ # These combinators build their error message from the labels of what they
385+ # wrap. A repeated character class names the class once, regardless of `min`.
386+ @ lower "ASCII character in the range \" a\" to \" z\" "
387+ @ digit "ASCII character in the range \" 0\" to \" 9\" "
388+
389+ test "lookahead names the character class once" do
390+ assert lookahead_sliced ( "abc" ) == { :ok , [ "ab" ] , "c" , % { } , { 1 , 0 } , 2 }
391+
392+ assert lookahead_sliced ( "1ab" ) ==
393+ { :error , "expected " <> @ lower , "1ab" , % { } , { 1 , 0 } , 0 }
394+
395+ assert lookahead_sliced ( "a" ) == { :error , "expected " <> @ lower , "a" , % { } , { 1 , 0 } , 0 }
396+ end
397+
398+ test "lookahead_not names the character class once" do
399+ assert lookahead_not_sliced ( "12" ) == { :ok , [ "12" ] , "" , % { } , { 1 , 0 } , 2 }
400+
401+ assert lookahead_not_sliced ( "ab12" ) ==
402+ { :error , "did not expect " <> @ lower , "ab12" , % { } , { 1 , 0 } , 0 }
403+ end
404+
405+ test "choice names each character class once" do
406+ assert choice_sliced ( "abc" ) == { :ok , [ "abc" ] , "" , % { } , { 1 , 0 } , 3 }
407+ assert choice_sliced ( "12x" ) == { :ok , [ "12" ] , "x" , % { } , { 1 , 0 } , 2 }
408+
409+ assert choice_sliced ( "!" ) ==
410+ { :error , "expected #{ @ lower } or #{ @ digit } " , "!" , % { } , { 1 , 0 } , 0 }
411+ end
412+ end
413+
354414 describe "ignore/2 combinator at compile time" do
355415 defparsecp :compile_ignore , ignore ( string ( "TO" ) )
356416 defparsecp :compile_ignore_with_newline , ignore ( string ( "T\n O" ) )
0 commit comments