Commit b2c6315
Changed files (7)
lib/mpeg/parser.rb
@@ -0,0 +1,165 @@
+module Mpeg
+ class Base
+ def parse(string)
+ call(Input.new(string))
+ end
+
+ def repeat(min = 0, max = nil)
+ Repitition.new(self, min, max)
+ end
+
+ def |(parser)
+ Alternative.new(self, parser)
+ end
+
+ def >>(parser)
+ Sequence.new(self, parser)
+ end
+
+ def absent?
+ Lookahead.new(self, false)
+ end
+ end
+
+ class Slice
+ def initialize(position, string)
+ end
+ end
+
+ class Input
+ def initialize(string)
+ @scanner = StringScanner.new(string)
+ end
+
+ def matches?(pattern)
+ @scanner.match?(pattern)
+ end
+
+ def consume(characters)
+ position = @scanner.pos
+ slice = @scanner.scan(/(.|$){#{characters}}/m)
+
+ Slice.new(position, slice)
+ end
+ end
+
+ class Lookahead < Base
+ def initialize(parser, positive)
+ @parser = parser
+ @positive = positive
+ end
+ end
+
+ class Sequence < Base
+ def initialize(left, right)
+ @left = left
+ @right = right
+ end
+
+ def call(input)
+ @left.call(input) && @right.call(input)
+ end
+ end
+
+ class Alternative < Base
+ def initialize(left, right)
+ @left = left
+ @right = right
+ end
+
+ def call(input)
+ @left.call(input) || @right.call(input)
+ end
+ end
+
+ class Repitition < Base
+ def initialize(parser, min, max)
+ @parser = parser
+ @min = min
+ @max = max
+ end
+
+ def call(input)
+ occurrences = 0
+ loop do
+ break unless @parser.call(input)
+ occurrences += 1
+ return false if @max && occurrences > @max
+ end
+ occurrences >= @min
+ end
+ end
+
+ class Re < Base
+ def initialize(regex)
+ @regex = Regexp.new("[#{regex}]", Regexp::MULTILINE)
+ end
+
+ def call(input)
+ if input.matches?(@regex)
+ input.consume(1)
+ return true
+ end
+ false
+ end
+ end
+
+ class Str < Base
+ def initialize(string)
+ @length = string.size
+ @regex = Regexp.new(Regexp.escape(string))
+ end
+
+ def call(input)
+ if input.matches?(@regex)
+ input.consume(1)
+ return true
+ end
+ false
+ end
+ end
+
+ class Parser
+ def parse(string)
+ root.parse(string)
+ end
+
+ private
+
+ def root
+ alpha.repeat(1, nil) >> comparison_operator >> string
+ end
+
+ def alpha
+ Re.new('a-zA-Z')
+ end
+
+ def comparison_operator
+ equal | not_equal
+ end
+
+ def equal
+ str('eq')
+ end
+
+ def not_equal
+ str('ne')
+ end
+
+ def string
+ quote >> (str('\\') >> any | str('"').absent? >> any).repeat >> quote
+ end
+
+ def quote
+ str('"')
+ end
+
+ def str(string)
+ Str.new(string)
+ end
+
+ def any
+ Re.new('.')
+ end
+ end
+end
lib/mpeg.rb
@@ -1,3 +1,4 @@
+require "mpeg/parser"
require "mpeg/version"
module Mpeg
spec/parser_spec.rb
@@ -0,0 +1,9 @@
+RSpec.describe Mpeg::Parser do
+ subject { described_class.new }
+
+ describe "#parse" do
+ specify do
+ expect(subject.parse('name eq "mo"')).to be_truthy
+ end
+ end
+end
spec/spec_helper.rb
@@ -1,5 +1,6 @@
require "bundler/setup"
require "mpeg"
+require 'byebug'
RSpec.configure do |config|
# Enable flags like --only-failures and --next-failure
.gitignore
@@ -9,3 +9,4 @@
# rspec failure tracking
.rspec_status
+.byebug_history
Gemfile.lock
@@ -6,6 +6,7 @@ PATH
GEM
remote: https://rubygems.org/
specs:
+ byebug (11.0.1)
diff-lcs (1.3)
rake (10.5.0)
rspec (3.8.0)
@@ -27,6 +28,7 @@ PLATFORMS
DEPENDENCIES
bundler (~> 2.0)
+ byebug (~> 11.0)
mpeg!
rake (~> 10.0)
rspec (~> 3.0)
mpeg.gemspec
@@ -24,6 +24,7 @@ Gem::Specification.new do |spec|
spec.require_paths = ["lib"]
spec.add_development_dependency "bundler", "~> 2.0"
+ spec.add_development_dependency "byebug", "~> 11.0"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_development_dependency "rspec", "~> 3.0"
end