Skip to content

Commit

Permalink
Added YAML::PullParser and use it in YAML::Parser
Browse files Browse the repository at this point in the history
  • Loading branch information
Ary Borenszweig committed Sep 4, 2015
1 parent 3900e5a commit 170f859
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 63 deletions.
154 changes: 154 additions & 0 deletions spec/std/yaml/yaml_pull_parser_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
require "spec"
require "yaml"

class YAML::PullParser
def assert_stream
kind.should eq(EventKind::STREAM_START)
yield read_next
kind.should eq(EventKind::STREAM_END)
end

def assert_document
kind.should eq(EventKind::DOCUMENT_START)
read_next
yield
kind.should eq(EventKind::DOCUMENT_END)
read_next
end

def assert_sequence(anchor = nil)
kind.should eq(EventKind::SEQUENCE_START)
assert_anchor anchor
read_next
yield
kind.should eq(EventKind::SEQUENCE_END)
read_next
end

def assert_mapping(anchor = nil)
kind.should eq(EventKind::MAPPING_START)
assert_anchor anchor
read_next
yield
kind.should eq(EventKind::MAPPING_END)
read_next
end

def assert_scalar(value, anchor = nil)
kind.should eq(EventKind::SCALAR)
value.should eq(value)
assert_anchor anchor
read_next
end

def assert_alias(value)
kind.should eq(EventKind::ALIAS)
value.should eq(value)
read_next
end

def assert_anchor(anchor)
self.anchor.should eq(anchor) if anchor
end
end

module YAML
describe PullParser do
it "reads empty stream" do
parser = PullParser.new("")
parser.assert_stream { |kind| kind.should eq(EventKind::STREAM_END) }
end

it "reads an empty document" do
parser = PullParser.new("---\n...\n")
parser.assert_stream do
parser.assert_document do
parser.assert_scalar ""
end
end
end

it "reads a scalar" do
parser = PullParser.new("--- foo\n...\n")
parser.assert_stream do
parser.assert_document do
parser.assert_scalar "foo"
end
end
end

it "reads a sequence" do
parser = PullParser.new("---\n- 1\n- 2\n- 3\n")
parser.assert_stream do
parser.assert_document do
parser.assert_sequence do
parser.assert_scalar "1"
parser.assert_scalar "2"
parser.assert_scalar "3"
end
end
end
end

it "reads a scalar with an anchor" do
parser = PullParser.new("--- &foo bar\n...\n")
parser.assert_stream do
parser.assert_document do
parser.assert_scalar "bar", anchor: "foo"
end
end
end

it "reads a sequence with an anchor" do
parser = PullParser.new("--- &foo []\n")
parser.assert_stream do
parser.assert_document do
parser.assert_sequence(anchor: "foo") do
end
end
end
end

it "reads a mapping" do
parser = PullParser.new(%(---\nfoo: 1\nbar: 2\n))
parser.assert_stream do
parser.assert_document do
parser.assert_mapping do
parser.assert_scalar "foo"
parser.assert_scalar "1"
parser.assert_scalar "bar"
parser.assert_scalar "2"
end
end
end
end

it "reads a mapping with an anchor" do
parser = PullParser.new(%(---\n&lala {}\n))
parser.assert_stream do
parser.assert_document do
parser.assert_mapping(anchor: "lala") do
end
end
end
end

it "parses alias" do
parser = PullParser.new("--- *foo\n")
parser.assert_stream do
parser.assert_document do
parser.assert_alias "foo"
end
end
end

it "parses alias with anchor" do
parser = PullParser.new("--- *foo\n")
parser.assert_stream do
parser.assert_document do
parser.assert_alias "foo"
end
end
end
end
end
File renamed without changes.
96 changes: 33 additions & 63 deletions src/yaml/parser.cr
Original file line number Diff line number Diff line change
@@ -1,81 +1,69 @@
class YAML::Parser
def initialize(content)
@parser = Pointer(Void).malloc(LibYAML::PARSER_SIZE) as LibYAML::Parser*
@event = LibYAML::Event.new
@pull_parser = PullParser.new(content)
@anchors = {} of String => YAML::Type

LibYAML.yaml_parser_initialize(@parser)
LibYAML.yaml_parser_set_input_string(@parser, content, LibC::SizeT.cast(content.bytesize))

next_event
raise "Expected STREAM_START" unless @event.type == LibYAML::EventType::STREAM_START
end

def close
LibYAML.yaml_parser_delete(@parser)
LibYAML.yaml_event_delete(pointerof(@event))
@pull_parser.close
end

def parse_all
documents = [] of YAML::Type
loop do
next_event
case @event.type
when LibYAML::EventType::STREAM_END
case @pull_parser.read_next
when EventKind::STREAM_END
return documents
when LibYAML::EventType::DOCUMENT_START
when EventKind::DOCUMENT_START
documents << parse_document
else
raise "Unexpected event: #{@event.type}"
unexpected_event
end
end
end

def parse
next_event
case @event.type
when LibYAML::EventType::STREAM_END
case @pull_parser.read_next
when EventKind::STREAM_END
nil
when LibYAML::EventType::DOCUMENT_START
when EventKind::DOCUMENT_START
parse_document
else
raise "Unexpected event: #{@event.type}"
unexpected_event
end
end

def parse_document
next_event
@pull_parser.read_next
value = parse_node
next_event
raise "Expected DOCUMENT_END" unless @event.type == LibYAML::EventType::DOCUMENT_END
unless @pull_parser.read_next == EventKind::DOCUMENT_END
raise "Expected DOCUMENT_END"
end
value
end

def parse_node
case @event.type
when LibYAML::EventType::SCALAR
String.new(@event.data.scalar.value).tap do |scalar|
anchor scalar, &.scalar
end
when LibYAML::EventType::ALIAS
@anchors[String.new(@event.data.alias.anchor)]
when LibYAML::EventType::SEQUENCE_START
case @pull_parser.kind
when EventKind::SCALAR
anchor @pull_parser.value, @pull_parser.scalar_anchor
when EventKind::ALIAS
@anchors[@pull_parser.alias_anchor]
when EventKind::SEQUENCE_START
parse_sequence
when LibYAML::EventType::MAPPING_START
when EventKind::MAPPING_START
parse_mapping
else
raise "Unexpected event #{event_to_s(@event.type)}"
unexpected_event
end
end

def parse_sequence
sequence = [] of YAML::Type
anchor sequence, &.sequence_start
anchor sequence, @pull_parser.sequence_anchor

loop do
next_event
case @event.type
when LibYAML::EventType::SEQUENCE_END
case @pull_parser.read_next
when EventKind::SEQUENCE_END
return sequence
else
sequence << parse_node
Expand All @@ -86,42 +74,24 @@ class YAML::Parser
def parse_mapping
mapping = {} of YAML::Type => YAML::Type
loop do
next_event
case @event.type
when LibYAML::EventType::MAPPING_END
case @pull_parser.read_next
when EventKind::MAPPING_END
return mapping
else
key = parse_node
next_event
@pull_parser.read_next
value = parse_node
mapping[key] = value
end
end
end

def anchor(value)
anchor = yield(@event.data).anchor
@anchors[String.new(anchor)] = value if anchor
end

def event_to_s(event_type)
case event_type
when LibYAML::EventType::NONE then "NONE"
when LibYAML::EventType::STREAM_START then "STREAM_START"
when LibYAML::EventType::STREAM_END then "STREAM_END"
when LibYAML::EventType::DOCUMENT_START then "DOCUMENT_START"
when LibYAML::EventType::DOCUMENT_END then "DOCUMENT_END"
when LibYAML::EventType::ALIAS then "ALIAS"
when LibYAML::EventType::SCALAR then "SCALAR"
when LibYAML::EventType::SEQUENCE_START then "SEQUENCE_START"
when LibYAML::EventType::SEQUENCE_END then "SEQUENCE_END"
when LibYAML::EventType::MAPPING_START then "MAPPING_START"
when LibYAML::EventType::MAPPING_END then "MAPPING_END"
end
def anchor(value, anchor)
@anchors[anchor] = value if anchor
value
end

def next_event
LibYAML.yaml_event_delete(pointerof(@event))
LibYAML.yaml_parser_parse(@parser, pointerof(@event))
private def unexpected_event
raise "Unexpected event: #{@pull_parser.kind}"
end
end
64 changes: 64 additions & 0 deletions src/yaml/pull_parser.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
class YAML::PullParser
def initialize(content)
@parser = Pointer(Void).malloc(LibYAML::PARSER_SIZE) as LibYAML::Parser*
@event = LibYAML::Event.new

LibYAML.yaml_parser_initialize(@parser)
LibYAML.yaml_parser_set_input_string(@parser, content, LibC::SizeT.cast(content.bytesize))

read_next
raise "Expected STREAM_START" unless @event.type == LibYAML::EventType::STREAM_START
end

def kind
@event.type
end

def value
String.new(@event.data.scalar.value)
end

def anchor
case kind
when LibYAML::EventType::SCALAR
scalar_anchor
when LibYAML::EventType::SEQUENCE_START
sequence_anchor
when LibYAML::EventType::MAPPING_START
mapping_anchor
else
nil
end
end

def scalar_anchor
read_anchor @event.data.scalar.anchor
end

def sequence_anchor
read_anchor @event.data.sequence_start.anchor
end

def mapping_anchor
read_anchor @event.data.mapping_start.anchor
end

def alias_anchor
read_anchor @event.data.alias.anchor
end

def read_next
LibYAML.yaml_event_delete(pointerof(@event))
LibYAML.yaml_parser_parse(@parser, pointerof(@event))
kind
end

def close
LibYAML.yaml_parser_delete(@parser)
LibYAML.yaml_event_delete(pointerof(@event))
end

private def read_anchor(anchor)
anchor ? String.new(anchor) : nil
end
end
1 change: 1 addition & 0 deletions src/yaml/yaml.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ require "./*"

module YAML
alias Type = String | Hash(Type, Type) | Array(Type) | Nil
alias EventKind = LibYAML::EventType

def self.load(data)
parser = YAML::Parser.new(data)
Expand Down

0 comments on commit 170f859

Please sign in to comment.