Add first version of ruby bindings.
This commit is contained in:
parent
ae0158a1cd
commit
6368214fce
14 changed files with 508 additions and 0 deletions
3
src/bindings/ruby/.gitignore
vendored
Normal file
3
src/bindings/ruby/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
/tmp/
|
||||||
|
/lib/hammer/hammer_ext.bundle
|
||||||
|
/Gemfile.lock
|
||||||
9
src/bindings/ruby/Gemfile
Normal file
9
src/bindings/ruby/Gemfile
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
|
gemspec
|
||||||
|
|
||||||
|
gem 'rake'
|
||||||
|
|
||||||
|
group :test do
|
||||||
|
# ...
|
||||||
|
end
|
||||||
72
src/bindings/ruby/README.md
Normal file
72
src/bindings/ruby/README.md
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
# hammer-parser
|
||||||
|
|
||||||
|
Ruby bindings for [hammer](https://github.com/UpstandingHackers/hammer), a parsing library.
|
||||||
|
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
* I called the gem `hammer-parser`, since there already is a [gem named `hammer`](https://rubygems.org/gems/hammer).
|
||||||
|
|
||||||
|
* C extension not really needed at the moment, if we don't mind hardcoding the token types in the ruby code.
|
||||||
|
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
1. `cd src/bindings/ruby`.
|
||||||
|
|
||||||
|
2. Run `bundle install` to install dependencies.
|
||||||
|
|
||||||
|
3. Run `rake compile` to compile the C extension.
|
||||||
|
|
||||||
|
4. Run `irb -I ./lib -r hammer` to open `irb` with hammer loaded.
|
||||||
|
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Building a parser
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
parser = Hammer::Parser.build {
|
||||||
|
token 'Hello '
|
||||||
|
choice {
|
||||||
|
token 'Mom'
|
||||||
|
token 'Dad'
|
||||||
|
}
|
||||||
|
token '!'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Also possible:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
parser = Hammer::ParserBuilder.new
|
||||||
|
.token('Hello ')
|
||||||
|
.choice(Hammer::Parser::Token.new('Mom'), Hammer::Parser::Token.new('Dad'))
|
||||||
|
.token('!')
|
||||||
|
.build
|
||||||
|
```
|
||||||
|
|
||||||
|
More like hammer in C:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
h = Hammer::Parser
|
||||||
|
parser = h.sequence(h.token('Hello'), h.choice(h.token('Mom'), h.token('Dad')), h.token('!'))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parsing
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
parser.parse 'Hello Mom!'
|
||||||
|
=> true
|
||||||
|
parser.parse 'Hello Someone!'
|
||||||
|
=> false
|
||||||
|
```
|
||||||
|
|
||||||
|
Currently you only get `true` or `false` depending on whether the parse succeeded or failed.
|
||||||
|
There's no way to access the parsed data yet.
|
||||||
8
src/bindings/ruby/Rakefile
Normal file
8
src/bindings/ruby/Rakefile
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
require 'rake/extensiontask'
|
||||||
|
|
||||||
|
#spec = Gem::Specification.load('hammer-parser-ruby.gemspec')
|
||||||
|
#Rake::ExtensionTask.new('hammer_ext', spec)
|
||||||
|
|
||||||
|
Rake::ExtensionTask.new 'hammer_ext' do |ext|
|
||||||
|
ext.lib_dir = 'lib/hammer'
|
||||||
|
end
|
||||||
9
src/bindings/ruby/ext/hammer_ext/extconf.rb
Normal file
9
src/bindings/ruby/ext/hammer_ext/extconf.rb
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
require 'mkmf'
|
||||||
|
|
||||||
|
extension_name = 'hammer_ext'
|
||||||
|
dir_config extension_name
|
||||||
|
|
||||||
|
abort 'ERROR: missing hammer library' unless have_library 'hammer'
|
||||||
|
abort 'ERROR: missing hammer.h' unless have_header 'hammer.h'
|
||||||
|
|
||||||
|
create_makefile extension_name
|
||||||
6
src/bindings/ruby/ext/hammer_ext/hammer_ext.c
Normal file
6
src/bindings/ruby/ext/hammer_ext/hammer_ext.c
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#include "token_type.h"
|
||||||
|
|
||||||
|
void Init_hammer_ext(void)
|
||||||
|
{
|
||||||
|
Init_token_type();
|
||||||
|
}
|
||||||
6
src/bindings/ruby/ext/hammer_ext/hammer_ext.h
Normal file
6
src/bindings/ruby/ext/hammer_ext/hammer_ext.h
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#ifndef HAMMER_EXT__H
|
||||||
|
#define HAMMER_EXT__H
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
#endif
|
||||||
20
src/bindings/ruby/ext/hammer_ext/token_type.c
Normal file
20
src/bindings/ruby/ext/hammer_ext/token_type.c
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#include <ruby.h>
|
||||||
|
#include <hammer.h>
|
||||||
|
|
||||||
|
#include "token_type.h"
|
||||||
|
|
||||||
|
#define DefineHammerInternalConst(name) rb_define_const(mHammerInternal, #name, INT2FIX(name));
|
||||||
|
|
||||||
|
void Init_token_type(void)
|
||||||
|
{
|
||||||
|
VALUE mHammer = rb_define_module("Hammer");
|
||||||
|
VALUE mHammerInternal = rb_define_module_under(mHammer, "Internal");
|
||||||
|
|
||||||
|
DefineHammerInternalConst(TT_NONE);
|
||||||
|
DefineHammerInternalConst(TT_BYTES);
|
||||||
|
DefineHammerInternalConst(TT_SINT);
|
||||||
|
DefineHammerInternalConst(TT_UINT);
|
||||||
|
DefineHammerInternalConst(TT_SEQUENCE);
|
||||||
|
DefineHammerInternalConst(TT_ERR);
|
||||||
|
DefineHammerInternalConst(TT_USER);
|
||||||
|
}
|
||||||
6
src/bindings/ruby/ext/hammer_ext/token_type.h
Normal file
6
src/bindings/ruby/ext/hammer_ext/token_type.h
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#ifndef HAMMER_EXT_TOKEN_TYPE__H
|
||||||
|
#define HAMMER_EXT_TOKEN_TYPE__H
|
||||||
|
|
||||||
|
void Init_token_type(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
23
src/bindings/ruby/hammer-parser.gemspec
Normal file
23
src/bindings/ruby/hammer-parser.gemspec
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
#encoding: UTF-8
|
||||||
|
Gem::Specification.new do |s|
|
||||||
|
s.name = 'hammer-parser'
|
||||||
|
s.version = '0.1.0'
|
||||||
|
s.summary = 'Ruby bindings to the hammer parsing library.'
|
||||||
|
s.description = s.summary # TODO: longer description?
|
||||||
|
s.authors = ['Meredith L. Patterson', 'TQ Hirsch', 'Jakob Rath']
|
||||||
|
# TODO:
|
||||||
|
# s.email = ...
|
||||||
|
# s.homepage = ...
|
||||||
|
|
||||||
|
files = []
|
||||||
|
files << 'README.md'
|
||||||
|
files << Dir['{lib,test}/**/*.rb']
|
||||||
|
s.files = files
|
||||||
|
s.test_files = s.files.select { |path| path =~ /^test\/.*_test.rb/ }
|
||||||
|
|
||||||
|
s.require_paths = %w[lib]
|
||||||
|
|
||||||
|
s.add_dependency 'ffi', '~> 1.9'
|
||||||
|
s.add_dependency 'docile', '~> 1.1' # TODO: Find a way to make this optional
|
||||||
|
end
|
||||||
|
|
||||||
65
src/bindings/ruby/lib/hammer.rb
Normal file
65
src/bindings/ruby/lib/hammer.rb
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
require 'hammer/hammer_ext'
|
||||||
|
require 'hammer/internal'
|
||||||
|
require 'hammer/parser'
|
||||||
|
require 'hammer/parser_builder'
|
||||||
|
|
||||||
|
# TODO:
|
||||||
|
# Probably need to rename this file to 'hammer-parser.rb', so
|
||||||
|
# people can use "require 'hammer-parser'" in their code.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Put tests in test/ directory.
|
||||||
|
|
||||||
|
parser = Hammer::Parser.build do
|
||||||
|
token 'blah'
|
||||||
|
ch 'a'
|
||||||
|
choice {
|
||||||
|
sequence {
|
||||||
|
token 'abc'
|
||||||
|
}
|
||||||
|
token 'def'
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
p parser
|
||||||
|
|
||||||
|
if parser
|
||||||
|
p parser.parse 'blahaabcd'
|
||||||
|
p parser.parse 'blahadefd'
|
||||||
|
p parser.parse 'blahablad'
|
||||||
|
p parser.parse 'blaha'
|
||||||
|
p parser.parse 'blah'
|
||||||
|
end
|
||||||
|
|
||||||
|
parser = Hammer::Parser::Sequence.new(
|
||||||
|
Hammer::Parser::Token.new('Hello '),
|
||||||
|
Hammer::Parser::Choice.new(
|
||||||
|
Hammer::Parser::Token.new('Mom'),
|
||||||
|
Hammer::Parser::Token.new('Dad')
|
||||||
|
),
|
||||||
|
Hammer::Parser::Token.new('!')
|
||||||
|
)
|
||||||
|
p parser.parse 'Hello Mom!'
|
||||||
|
|
||||||
|
parser = Hammer::Parser.build {
|
||||||
|
token 'Hello '
|
||||||
|
choice {
|
||||||
|
token 'Mom'
|
||||||
|
token 'Dad'
|
||||||
|
}
|
||||||
|
token '!'
|
||||||
|
}
|
||||||
|
p parser.parse 'Hello Mom!'
|
||||||
|
|
||||||
|
parser = Hammer::ParserBuilder.new
|
||||||
|
.token('Hello ')
|
||||||
|
.choice(Hammer::Parser::Token.new('Mom'), Hammer::Parser::Token.new('Dad'))
|
||||||
|
.token('!')
|
||||||
|
.build
|
||||||
|
p parser.parse 'Hello Mom!'
|
||||||
|
|
||||||
|
# not yet working
|
||||||
|
#h = Hammer::Parser
|
||||||
|
#parser = h.sequence(h.token('Hello'), h.choice(h.token('Mom'), h.token('Dad')), h.token('!'))
|
||||||
|
#p parser.parse 'Hello Mom!'
|
||||||
60
src/bindings/ruby/lib/hammer/internal.rb
Normal file
60
src/bindings/ruby/lib/hammer/internal.rb
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
require 'ffi'
|
||||||
|
|
||||||
|
module Hammer
|
||||||
|
module Internal
|
||||||
|
extend FFI::Library
|
||||||
|
|
||||||
|
ffi_lib 'libhammer.dylib'
|
||||||
|
|
||||||
|
# run a parser
|
||||||
|
attach_function :h_parse, [:pointer, :string, :size_t], :pointer
|
||||||
|
|
||||||
|
# build a parser
|
||||||
|
attach_function :h_token, [:string, :size_t], :pointer
|
||||||
|
attach_function :h_ch, [:uint8], :pointer
|
||||||
|
attach_function :h_ch_range, [:uint8, :uint8], :pointer
|
||||||
|
attach_function :h_int_range, [:int64, :int64], :pointer
|
||||||
|
attach_function :h_bits, [:size_t, :bool], :pointer
|
||||||
|
attach_function :h_int64, [], :pointer
|
||||||
|
attach_function :h_int32, [], :pointer
|
||||||
|
attach_function :h_int16, [], :pointer
|
||||||
|
attach_function :h_int8, [], :pointer
|
||||||
|
attach_function :h_uint64, [], :pointer
|
||||||
|
attach_function :h_uint32, [], :pointer
|
||||||
|
attach_function :h_uint16, [], :pointer
|
||||||
|
attach_function :h_uint8, [], :pointer
|
||||||
|
attach_function :h_whitespace, [:pointer], :pointer
|
||||||
|
attach_function :h_left, [:pointer, :pointer], :pointer
|
||||||
|
attach_function :h_right, [:pointer, :pointer], :pointer
|
||||||
|
attach_function :h_middle, [:pointer, :pointer, :pointer], :pointer
|
||||||
|
# h_action
|
||||||
|
# h_in
|
||||||
|
# h_not_in
|
||||||
|
attach_function :h_end_p, [], :pointer
|
||||||
|
attach_function :h_nothing_p, [], :pointer
|
||||||
|
attach_function :h_sequence, [:varargs], :pointer
|
||||||
|
attach_function :h_choice, [:varargs], :pointer
|
||||||
|
attach_function :h_butnot, [:pointer, :pointer], :pointer
|
||||||
|
attach_function :h_difference, [:pointer, :pointer], :pointer
|
||||||
|
attach_function :h_xor, [:pointer, :pointer], :pointer
|
||||||
|
attach_function :h_many, [:pointer], :pointer
|
||||||
|
attach_function :h_many1, [:pointer], :pointer
|
||||||
|
# h_repeat_n
|
||||||
|
# h_optional
|
||||||
|
# h_ignore
|
||||||
|
# h_sepBy
|
||||||
|
# h_sepBy1
|
||||||
|
# h_epsilon_p
|
||||||
|
# h_length_value
|
||||||
|
# h_attr_bool
|
||||||
|
# h_and
|
||||||
|
# h_not
|
||||||
|
# h_indirect
|
||||||
|
# h_bind_indirect
|
||||||
|
|
||||||
|
# free the parse result
|
||||||
|
# h_parse_result_free
|
||||||
|
|
||||||
|
# TODO: Does the HParser* need to be freed?
|
||||||
|
end
|
||||||
|
end
|
||||||
146
src/bindings/ruby/lib/hammer/parser.rb
Normal file
146
src/bindings/ruby/lib/hammer/parser.rb
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
module Hammer
|
||||||
|
class Parser
|
||||||
|
|
||||||
|
# Don't create new instances with Hammer::Parser.new,
|
||||||
|
# use the constructor methods instead (i.e. Hammer::Parser.int64 etc.)
|
||||||
|
def initialize
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse(data)
|
||||||
|
raise RuntimeError, '@h_parser is nil' if @h_parser.nil?
|
||||||
|
raise ArgumentError, 'expecting a String' unless data.is_a? String # TODO: Not needed, FFI checks that.
|
||||||
|
result = Hammer::Internal.h_parse(@h_parser, data, data.length);
|
||||||
|
# TODO: Do something with the data
|
||||||
|
!result.null?
|
||||||
|
end
|
||||||
|
|
||||||
|
class Token < Parser
|
||||||
|
def initialize(string)
|
||||||
|
@h_parser = Hammer::Internal.h_token(string, string.length)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Ch < Parser
|
||||||
|
def initialize(char)
|
||||||
|
# TODO: Really? Should probably accept Fixnum in appropriate range
|
||||||
|
# Also, char.ord gives unexptected results if you pass e.g. Japanese characters: '今'.ord == 20170; Hammer::Parser::Ch.new('今').parse(202.chr) == true
|
||||||
|
# Not really unexpected though, since 20170 & 255 == 202.
|
||||||
|
# But probably it's better to use Ch for Fixnum in 0..255 only, and only Token for strings.
|
||||||
|
raise ArgumentError, 'expecting a one-character String' unless char.is_a?(String) && char.length == 1
|
||||||
|
@h_parser = Hammer::Internal.h_ch(char.ord)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Sequence < Parser
|
||||||
|
def initialize(*parsers)
|
||||||
|
#args = []
|
||||||
|
#parsers.each { |p| args += [:pointer, p.h_parser] }
|
||||||
|
args = parsers.flat_map { |p| [:pointer, p.h_parser] }
|
||||||
|
@h_parser = Hammer::Internal.h_sequence(*args, :pointer, nil)
|
||||||
|
@sub_parsers = parsers # store them so they don't get garbage-collected (probably not needed, though)
|
||||||
|
# TODO: Use (managed?) FFI struct instead of void pointers
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Choice < Parser
|
||||||
|
def initialize(*parsers)
|
||||||
|
#args = []
|
||||||
|
#parsers.each { |p| args += [:pointer, p.h_parser] }
|
||||||
|
args = parsers.flat_map { |p| [:pointer, p.h_parser] }
|
||||||
|
@h_parser = Hammer::Internal.h_choice(*args, :pointer, nil)
|
||||||
|
@sub_parsers = parsers # store them so they don't get garbage-collected (probably not needed, though)
|
||||||
|
# TODO: Use (managed?) FFI struct instead of void pointers
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Define parsers that take some number of other parsers
|
||||||
|
# TODO: Maybe use -1 for variable number, and use this for Sequence and Choice too
|
||||||
|
# TODO: Refactor this code as a method? And call it like: define_parser :Int64, :h_int64, 0
|
||||||
|
[
|
||||||
|
[:Int64, :h_int64, 0],
|
||||||
|
[:Int32, :h_int32, 0],
|
||||||
|
[:Int16, :h_int16, 0],
|
||||||
|
[:Int8, :h_int8, 0],
|
||||||
|
[:UInt64, :h_uint64, 0],
|
||||||
|
[:UInt32, :h_uint32, 0],
|
||||||
|
[:UInt16, :h_uint16, 0],
|
||||||
|
[:UInt8, :h_uint8, 0],
|
||||||
|
[:Whitespace, :h_whitespace, 1],
|
||||||
|
[:Left, :h_left, 2],
|
||||||
|
[:Right, :h_right, 2],
|
||||||
|
[:Middle, :h_middle, 3],
|
||||||
|
[:End, :h_end_p, 0],
|
||||||
|
[:Nothing, :h_nothing_p, 0],
|
||||||
|
[:ButNot, :h_butnot, 2],
|
||||||
|
[:Difference, :h_difference, 2],
|
||||||
|
[:Xor, :h_xor, 2],
|
||||||
|
[:Many, :h_many, 1],
|
||||||
|
[:Many1, :h_many1, 1]
|
||||||
|
].each do |class_name, h_function_name, parameter_count|
|
||||||
|
# Create new subclass of Hammer::Parser
|
||||||
|
klass = Class.new(Hammer::Parser) do
|
||||||
|
# Need to use define_method instead of def to be able to access h_function_name in the method's body
|
||||||
|
define_method :initialize do |*parsers|
|
||||||
|
# Checking parameter_count is not really needed, since the h_* methods will complain anyways
|
||||||
|
@h_parser = Hammer::Internal.send(h_function_name, *parsers.map(&:h_parser))
|
||||||
|
# TODO: Do we need to store sub-parsers to prevent them from getting garbage-collected?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
# Register class with name Hammer::Parser::ClassName
|
||||||
|
Hammer::Parser.const_set class_name, klass
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO:
|
||||||
|
# Hammer::Parser::Token.new('...') is a bit too long. Find a shorter way to use the parsers.
|
||||||
|
# Maybe:
|
||||||
|
# class Hammer::Parser
|
||||||
|
# def self.token(*args)
|
||||||
|
# Hammer::Parser::Token.new(*args)
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
# Can create functions like that automatically. Usage:
|
||||||
|
# h = Hammer::Parser
|
||||||
|
# parser = h.sequence(h.token('blah'), h.token('other_token'))
|
||||||
|
# Looks almost like hammer in C!
|
||||||
|
|
||||||
|
# Defines a parser constructor with the given name.
|
||||||
|
# Options:
|
||||||
|
# hammer_function: name of the hammer function to call (default: 'h_'+name)
|
||||||
|
def self.define_parser(name, options = {})
|
||||||
|
hammer_function = options[:hammer_function] || ('h_' + name.to_s)
|
||||||
|
|
||||||
|
# Define a new class method
|
||||||
|
define_singleton_method name do |*parsers|
|
||||||
|
#args = parsers.map { |p| p.instance_variable_get :@h_parser }
|
||||||
|
h_parser = Hammer::Internal.send hammer_function, *parsers.map(&:h_parser)
|
||||||
|
|
||||||
|
parser = Hammer::Parser.new
|
||||||
|
parser.instance_variable_set :@h_parser, h_parser
|
||||||
|
return parser
|
||||||
|
end
|
||||||
|
end
|
||||||
|
private_class_method :define_parser
|
||||||
|
|
||||||
|
define_parser :int64
|
||||||
|
define_parser :int32
|
||||||
|
define_parser :int16
|
||||||
|
define_parser :int8
|
||||||
|
define_parser :uint64
|
||||||
|
define_parser :uint32
|
||||||
|
define_parser :uint16
|
||||||
|
define_parser :uint8
|
||||||
|
define_parser :whitespace
|
||||||
|
define_parser :left
|
||||||
|
define_parser :right
|
||||||
|
define_parser :middle
|
||||||
|
define_parser :end
|
||||||
|
define_parser :nothing
|
||||||
|
define_parser :butnot
|
||||||
|
define_parser :difference
|
||||||
|
define_parser :xor
|
||||||
|
define_parser :many
|
||||||
|
define_parser :many1
|
||||||
|
|
||||||
|
attr_reader :h_parser
|
||||||
|
end
|
||||||
|
end
|
||||||
75
src/bindings/ruby/lib/hammer/parser_builder.rb
Normal file
75
src/bindings/ruby/lib/hammer/parser_builder.rb
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
# TODO: Find a way to make docile an optional dependency
|
||||||
|
# (autoload for this file? and throw some informative error when docile isn't available.
|
||||||
|
# should also check gem version with a 'gem' call and appropriate version specifier.)
|
||||||
|
require 'docile'
|
||||||
|
|
||||||
|
module Hammer
|
||||||
|
|
||||||
|
class Parser
|
||||||
|
def self.build(&block)
|
||||||
|
ParserBuilder.new.sequence(&block).build
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO: Is this even useful for "real" usage?
|
||||||
|
class ParserBuilder
|
||||||
|
attr_reader :parsers
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@parsers = []
|
||||||
|
# TODO: Store an aggregator, e.g.:
|
||||||
|
# @aggregator = Hammer::Parser::Sequence
|
||||||
|
# Sequence is the default, set to Hammer::Parser::Choice for choice() calls
|
||||||
|
# In the build method, use @aggregator.new(*@parsers) to build the final parser.
|
||||||
|
end
|
||||||
|
|
||||||
|
def build
|
||||||
|
if @parsers.length > 1
|
||||||
|
Hammer::Parser::Sequence.new(*@parsers)
|
||||||
|
else
|
||||||
|
@parsers.first
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Need to check if that's really needed
|
||||||
|
def call(parser)
|
||||||
|
@parsers << parser
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def token(str)
|
||||||
|
#@h_parsers << Hammer::Internal.h_token(str, str.length)
|
||||||
|
@parsers << Hammer::Parser::Token.new(str)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
def ch(char)
|
||||||
|
#@h_parsers << Hammer::Internal.h_ch(char.ord)
|
||||||
|
@parsers << Hammer::Parser::Ch.new(char)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
# can call it either as ParserBuiler.new.sequence(parser1, parser2, parser3)
|
||||||
|
# or as Parser.build { sequence { call parser1; call parser2; call parser3 } }
|
||||||
|
def sequence(*parsers, &block)
|
||||||
|
@parsers += parsers
|
||||||
|
@parsers << Docile.dsl_eval(ParserBuilder.new, &block).build if block_given?
|
||||||
|
return self
|
||||||
|
#builder = Hammer::ParserBuilder.new
|
||||||
|
#builder.instance_eval &block
|
||||||
|
#@parsers << Hammer::Parser::Sequence.new(*builder.parsers)
|
||||||
|
## TODO: Save original receiver and redirect missing methods!
|
||||||
|
end
|
||||||
|
|
||||||
|
def choice(*parsers, &block)
|
||||||
|
if block_given?
|
||||||
|
parsers += Docile.dsl_eval(ParserBuilder.new, &block).parsers
|
||||||
|
end
|
||||||
|
@parsers << Hammer::Parser::Choice.new(*parsers)
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
Loading…
Add table
Add a link
Reference in a new issue