require "../spec_helper" include Parcom # NOTE: WORK IN PROGRESS alias JSONType = Nil \ | Bool \ | Int64 \ | Float64 \ | String \ | Array(JSONValue) \ | Hash(String, JSONValue) struct JSONValue property data : JSONType def initialize(@data) end end def json_null Parser.token_sequence("null".chars).map_const(JSONValue.new(nil)) end def json_bool t = Parser.token_sequence("true".chars).map_const(true) f = Parser.token_sequence("false".chars).map_const(false) (t | f).map { |b| JSONValue.new(b) } end # Only standard decimal ints for now #def json_number # base_num = Parser(Char, Char).satisfy(&.ascii_number?).some.map do |cs| # cs.join.to_i64 # end # sign = Parser.token('-').map_const(-1).recover(1) # (sign + base_num).map { |s, n| JSONValue.new(s.to_i64 * n) } #end def json_number digits = Parser(Char, Char).satisfy(&.ascii_number?).many sign = Parser.token('-').map_const(-1).recover(1) point = Parser.token('.').optional sign.and_then do |s| digits.and_then do |front| point.and_then do |p| digits.and_then do |back| if front.empty? && back.empty? Parser(Char, JSONValue).flunk else x = case when front.empty? && !p.nil? # {[], '.', _} # 0 <= x < 1 "0.#{back.join}".to_f64 when back.empty? #{_, _, []} # whole number front.join.to_i64 else #when !front.empty? && !p.nil? && !back.empty? # some other float "#{front.join}.#{back.join}".to_f64 #else # raise "json_number, Should be unreachable: #{front} #{p} #{back}" end x = x.to_i64 if x.is_a?(Float64) && (x % 1).zero? Parser(Char, JSONValue).pure(JSONValue.new(x * s)) end end end end end end def json_value : Parser(Char, JSONValue) Parser.first_of([ json_null, json_bool, json_number, ]) end describe "example: JSON parsing" do describe "json_null" do it "parses the string 'null' and returns nil when successful" do result = json_null.parse(Tokens.from_string("null")) result.value.data.should be_nil result.tokens.empty?.should be_true expect_raises(ParserFail) { json_null.parse(Tokens.from_string("")) } end end describe "json_bool" do it "parses the strings 'true' or 'false' and returns bool" do result = json_bool.parse(Tokens.from_string("true")) result.value.data.should be_true result.tokens.empty?.should be_true result = json_bool.parse(Tokens.from_string("false")) result.value.data.should be_false result.tokens.empty?.should be_true expect_raises(ParserFail) { json_bool.parse(Tokens.from_string("")) } end end describe "json_number" do it "parses positive and negative integers" do {"42", "42.", "42.000000"}.each do |s| result = json_number.parse(Tokens.from_string(s)) result.value.data.should eq(42) result.value.data.should be_a(Int64) result.tokens.empty?.should be_true result = json_number.parse(Tokens.from_string("-#{s}")) result.value.data.should eq(-42) result.value.data.should be_a(Int64) result.tokens.empty?.should be_true end end it "parses positive and negative floats between -1 and 1" do {"0.1234", ".1234", ".12340000"}.each do |s| result = json_number.parse(Tokens.from_string(s)) result.value.data.should eq(0.1234) result.value.data.should be_a(Float64) result.tokens.empty?.should be_true result = json_number.parse(Tokens.from_string("-#{s}")) result.value.data.should eq(-0.1234) result.value.data.should be_a(Float64) result.tokens.empty?.should be_true end end it "parses positive and negative floats with a whole-number component" do {"12.34", "0012.3400"}.each do |s| result = json_number.parse(Tokens.from_string(s)) result.value.data.should eq(12.34) result.value.data.should be_a(Float64) result.tokens.empty?.should be_true result = json_number.parse(Tokens.from_string("-#{s}")) result.value.data.should eq(-12.34) result.value.data.should be_a(Float64) result.tokens.empty?.should be_true end end it "parses 0 as an int in various forms" do {"0", "0.0", "0000", ".000", "000.", "-0", "-0.", "-.0", "-0.0"}.each do |s| result = json_number.parse(Tokens.from_string(s)) result.value.data.should eq(0) result.value.data.should be_a(Int64) result.tokens.empty?.should be_true end end it "does not parse '.' or '-.' as 0" do expect_raises(ParserFail) { json_number.parse(Tokens.from_string(".")) } end it "fails when input if not a number" do expect_raises(ParserFail) { json_number.parse(Tokens.from_string("")) } expect_raises(ParserFail) { json_number.parse(Tokens.from_string("foo")) } end end describe "json_value" do it "parses null" do result = json_value.parse(Tokens.from_string("null")) result.value.data.should be_nil result.tokens.empty?.should be_true end it "parses bools" do result = json_value.parse(Tokens.from_string("true")) result.value.data.should be_true result.tokens.empty?.should be_true result = json_value.parse(Tokens.from_string("false")) result.value.data.should be_false result.tokens.empty?.should be_true end it "parses ints" do result = json_value.parse(Tokens.from_string("42")) result.value.data.should eq(42) result.value.data.should be_a(Int64) result.tokens.empty?.should be_true result = json_value.parse(Tokens.from_string("-42")) result.value.data.should eq(-42) result.value.data.should be_a(Int64) result.tokens.empty?.should be_true end it "parses floats" do result = json_value.parse(Tokens.from_string("12.34")) result.value.data.should eq(12.34) result.value.data.should be_a(Float64) result.tokens.empty?.should be_true result = json_value.parse(Tokens.from_string("-12.34")) result.value.data.should eq(-12.34) result.value.data.should be_a(Float64) result.tokens.empty?.should be_true end end end