Loading lib/jsduck/js_literal_parser.rb 0 → 100644 +105 −0 Original line number Diff line number Diff line require 'jsduck/lexer' module JsDuck # Parser for JavaScript literals: numbers, strings, booleans, # regexes, arrays, objects. # # Almost like a JSON parser, but regexes are also valid values and # object keys don't need to be quoted. class JsLiteralParser def initialize(input) @lex = Lexer.new(input) end # Parses a literal. # # Returns a Ruby hash representing this literal. For example parsing this: # # [5, "foo"] # # Returns the following structure: # # {:type => :array, :value => [ # {:type => :number, :value => "5"}, # {:type => :string, :value => "foo"} # ]} # def literal if look(:number) match(:number) elsif look(:string) match(:string) elsif look(:regex) match(:regex) elsif look("[") array_literal elsif look("{") object_literal elsif look(:ident) && (look("true") || look("false") || look("undefined") || look("null")) match(:ident) end end def array_literal match("[") r = [] while (lit = literal) r << lit break unless look(",") match(",") end return unless look("]") match("]") return {:type => :array, :value => r} end def object_literal match("{") r = [] while (lit = object_literal_pair) r << lit break unless look(",") match(",") end return unless look("}") match("}") return {:type => :object, :value => r} end def object_literal_pair if look(:ident) key = match(:ident) elsif look(:string) key = match(:string) else return end return unless look(":") match(":") value = literal return unless value return {:key => key, :value => value} end # Matches all arguments, returns the value of last match # When the whole sequence doesn't match, throws exception def match(*args) if look(*args) last = nil args.length.times { last = @lex.next(true) } last else throw "Expected: " + args.join(", ") end end def look(*args) @lex.look(*args) end end end spec/js_literal_parser_spec.rb 0 → 100644 +48 −0 Original line number Diff line number Diff line require "jsduck/js_literal_parser" describe JsDuck::JsLiteralParser do def parse(source) JsDuck::JsLiteralParser.new(source).literal end it "parses number" do r = parse("5") r[:type].should == :number r[:value].should == "5" end it "parses string" do r = parse("'foo'") r[:type].should == :string r[:value].should == "foo" end it "parses regex" do r = parse("/[a-z]/i") r[:type].should == :regex r[:value].should == "/[a-z]/i" end it "parses array" do r = parse("[1, 2]") r[:type].should == :array v = r[:value] v.length.should == 2 v[0][:value].should == "1" v[1][:value].should == "2" end it "parses object" do r = parse("{foo: 1, bar: [2, 3]}") r[:type].should == :object v = r[:value] v.length.should == 2 v[0][:key][:type].should == :ident v[0][:key][:value].should == "foo" v[0][:value][:type].should == :number v[0][:value][:value].should == "1" end end Loading
lib/jsduck/js_literal_parser.rb 0 → 100644 +105 −0 Original line number Diff line number Diff line require 'jsduck/lexer' module JsDuck # Parser for JavaScript literals: numbers, strings, booleans, # regexes, arrays, objects. # # Almost like a JSON parser, but regexes are also valid values and # object keys don't need to be quoted. class JsLiteralParser def initialize(input) @lex = Lexer.new(input) end # Parses a literal. # # Returns a Ruby hash representing this literal. For example parsing this: # # [5, "foo"] # # Returns the following structure: # # {:type => :array, :value => [ # {:type => :number, :value => "5"}, # {:type => :string, :value => "foo"} # ]} # def literal if look(:number) match(:number) elsif look(:string) match(:string) elsif look(:regex) match(:regex) elsif look("[") array_literal elsif look("{") object_literal elsif look(:ident) && (look("true") || look("false") || look("undefined") || look("null")) match(:ident) end end def array_literal match("[") r = [] while (lit = literal) r << lit break unless look(",") match(",") end return unless look("]") match("]") return {:type => :array, :value => r} end def object_literal match("{") r = [] while (lit = object_literal_pair) r << lit break unless look(",") match(",") end return unless look("}") match("}") return {:type => :object, :value => r} end def object_literal_pair if look(:ident) key = match(:ident) elsif look(:string) key = match(:string) else return end return unless look(":") match(":") value = literal return unless value return {:key => key, :value => value} end # Matches all arguments, returns the value of last match # When the whole sequence doesn't match, throws exception def match(*args) if look(*args) last = nil args.length.times { last = @lex.next(true) } last else throw "Expected: " + args.join(", ") end end def look(*args) @lex.look(*args) end end end
spec/js_literal_parser_spec.rb 0 → 100644 +48 −0 Original line number Diff line number Diff line require "jsduck/js_literal_parser" describe JsDuck::JsLiteralParser do def parse(source) JsDuck::JsLiteralParser.new(source).literal end it "parses number" do r = parse("5") r[:type].should == :number r[:value].should == "5" end it "parses string" do r = parse("'foo'") r[:type].should == :string r[:value].should == "foo" end it "parses regex" do r = parse("/[a-z]/i") r[:type].should == :regex r[:value].should == "/[a-z]/i" end it "parses array" do r = parse("[1, 2]") r[:type].should == :array v = r[:value] v.length.should == 2 v[0][:value].should == "1" v[1][:value].should == "2" end it "parses object" do r = parse("{foo: 1, bar: [2, 3]}") r[:type].should == :object v = r[:value] v.length.should == 2 v[0][:key][:type].should == :ident v[0][:key][:value].should == "foo" v[0][:value][:type].should == :number v[0][:value][:value].should == "1" end end