parsby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,41 @@
1
+ require 'parsby'
2
+
3
+ # This is based on:
4
+ #
5
+ # RFC 4180: Common Format and MIME Type for Comma-Separated Values (CSV) Files
6
+ module Parsby::Example
7
+ module CsvParser
8
+ include Parsby::Combinators
9
+ extend self
10
+
11
+ def parse(io)
12
+ csv.parse io
13
+ end
14
+
15
+ define_combinator :csv do
16
+ many(record) < eof
17
+ end
18
+
19
+ define_combinator :record do
20
+ sep_by(lit(","), cell) < (eol | eof)
21
+ end
22
+
23
+ define_combinator :cell do
24
+ quoted_cell | non_quoted_cell
25
+ end
26
+
27
+ define_combinator :quoted_cell do
28
+ non_quote = join(many(any_char.that_fail(lit('"'))))
29
+ inner = sep_by(lit('""'), non_quote).fmap {|r| r.join '"' }
30
+ lit('"') > inner < lit('"')
31
+ end
32
+
33
+ define_combinator :non_quoted_cell do
34
+ join(many(any_char.that_fail(lit(",") | lit("\"") | eol)))
35
+ end
36
+
37
+ define_combinator :eol do
38
+ lit("\r\n") | lit("\n")
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,92 @@
1
+ require 'parsby'
2
+
3
+ module Parsby::Example
4
+ module JsonParser
5
+ include Parsby::Combinators
6
+ extend self
7
+
8
+ def parse(io)
9
+ (spaced(value) < eof).parse io
10
+ end
11
+
12
+ define_combinator :value do
13
+ null | bool | number | string | array | object
14
+ end
15
+
16
+ define_combinator :null do
17
+ lit("null") > pure(nil)
18
+ end
19
+
20
+ define_combinator :bool do
21
+ choice(
22
+ lit("true") > pure(true),
23
+ lit("false") > pure(false),
24
+ )
25
+ end
26
+
27
+ # This has been adopted as Parsby::Combinators#fractional_decimal, but
28
+ # we leave this definition here since this module is supposed to be an
29
+ # example of using Parsby, and this works well for showing how to use
30
+ # `group` and `fmap`.
31
+ define_combinator :number do
32
+ sign = lit("-") | lit("+")
33
+ group(
34
+ optional(sign),
35
+ decimal,
36
+ optional(group(
37
+ lit("."),
38
+ decimal,
39
+ )),
40
+ optional(group(
41
+ lit("e") | lit("E"),
42
+ optional(sign),
43
+ decimal,
44
+ )),
45
+ ).fmap do |(sign, whole, (_, fractional), (_, exponent_sign, exponent))|
46
+ n = whole
47
+ n += fractional.to_f / 10 ** fractional.to_s.length if fractional
48
+ n *= -1 if sign == "-"
49
+ if exponent
50
+ e = exponent
51
+ e *= -1 if exponent_sign == "-"
52
+ n *= 10 ** e
53
+ end
54
+ n
55
+ end
56
+ end
57
+
58
+ define_combinator :string do
59
+ between(lit('"'), lit('"'),
60
+ join(many(choice(
61
+ any_char.that_fails(lit('"') | lit("\\")),
62
+ lit("\\") > choice(
63
+ lit('"'),
64
+ lit("\\"),
65
+ lit("/"),
66
+ lit("f") > pure("\f"),
67
+ lit("b") > pure("\b"),
68
+ lit("r") > pure("\r"),
69
+ lit("n") > pure("\n"),
70
+ lit("t") > pure("\t"),
71
+ lit("u") > join(hex_digit * 4).fmap {|s| [s.hex].pack("U") },
72
+ ),
73
+ )))
74
+ )
75
+ end
76
+
77
+ define_combinator :array do
78
+ between(lit("["), ws > lit("]"), sep_by(lit(","), spaced(lazy { value })))
79
+ end
80
+
81
+ define_combinator :object do
82
+ between(lit("{"), ws > lit("}"),
83
+ sep_by(lit(","),
84
+ spaced(group(
85
+ string < spaced(lit(":")),
86
+ lazy { value }
87
+ )),
88
+ )
89
+ ).fmap(&:to_h)
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,135 @@
1
+ require "parsby"
2
+
3
+ module Parsby::Example
4
+ module LispParser
5
+ include Parsby::Combinators
6
+ extend self
7
+
8
+ def parse(io)
9
+ sexp_sequence.parse io
10
+ end
11
+
12
+ define_combinator :sexp_sequence do
13
+ many(spaced(sexp)) < eof
14
+ end
15
+
16
+ define_combinator :sexp, wrap: false do
17
+ splicer.start {|m| lazy { choice(m.end(abbrev), m.end(atom), m.end(list)) } }
18
+ end
19
+
20
+ # Add comments to definition of whitespace. whitespace is defined using
21
+ # whitespace_1, so we cover both with this.
22
+ define_combinator :whitespace_1 do
23
+ join(many_1(super() | comment))
24
+ end
25
+
26
+ define_combinator :comment do
27
+ lit(";") \
28
+ + join(many(any_char.that_fails(lit("\n")))) \
29
+ + (lit("\n") | (eof > pure("")))
30
+ end
31
+
32
+ # Parses sexps with abbreviations, like '(foo bar) or `(foo ,bar).
33
+ define_combinator :abbrev do
34
+ ~splicer.start do
35
+ choice(
36
+ lit("'") > sexp.fmap {|s| [:quote, [s, nil]]},
37
+ lit("`") > sexp.fmap {|s| [:quasiquote, [s, nil]]},
38
+ lit(",@") > sexp.fmap {|s| [:"unquote-splicing", [s, nil]]},
39
+ lit(",") > sexp.fmap {|s| [:unquote, [s, nil]]},
40
+ )
41
+ end
42
+ end
43
+
44
+ define_combinator :list do
45
+ braces = {"(" => ")", "[" => "]"}
46
+
47
+ ~splicer.start do |m|
48
+ m.end(char_in(braces.keys.join)).then do |opening_brace|
49
+ spaced(list_insides(m)) < m.end(lit(braces[opening_brace]))
50
+ end
51
+ end
52
+ end
53
+
54
+ define_combinator :list_insides do |splicing_marker|
55
+ optional(
56
+ group(
57
+ splicing_marker.end(sexp),
58
+ choice(
59
+ spaced(splicing_marker.end(lit("."))) > splicing_marker.end(sexp),
60
+ whitespace > lazy { list_insides(splicing_marker) },
61
+ ),
62
+ )
63
+ )
64
+ end
65
+
66
+ define_combinator :atom do
67
+ ~choice(number, string, self.nil, symbol)
68
+ end
69
+
70
+ define_combinator :nil, wrap: false do
71
+ splicer.start { ilit("nil") > pure(nil) }
72
+ end
73
+
74
+ define_combinator :symbol_char do
75
+ char_in(
76
+ [
77
+ *('a'..'z'),
78
+ *('A'..'Z'),
79
+ *('0'..'9'),
80
+ # Got list from R6RS; removed '.' for simplicity.
81
+ *%w(! $ % & * + - / : < = > ? @ ^ _ ~),
82
+ ].flatten.join
83
+ )
84
+ end
85
+
86
+ define_combinator :symbol do
87
+ ~splicer.start { join(many_1(symbol_char)).fmap(&:to_sym) }
88
+ end
89
+
90
+ define_combinator :hex_digit do
91
+ char_in(
92
+ [*("0".."9"), *("a".."f"), *("A".."F")]
93
+ .flatten
94
+ .join
95
+ )
96
+ end
97
+
98
+ define_combinator :escape_sequence do
99
+ lit("\\") > choice([
100
+ lit("\"") > pure("\""),
101
+ lit("n") > pure("\n"),
102
+ lit("t") > pure("\t"),
103
+ lit("r") > pure("\r"),
104
+ lit("x") > (hex_digit * 2)
105
+ .fmap {|(d1, d2)| (d1 + d2).to_i(16).chr },
106
+ lit("\\"),
107
+ ])
108
+ end
109
+
110
+ define_combinator :string do
111
+ ~splicer.start do
112
+ between(lit('"'), lit('"'),
113
+ join(many(choice(
114
+ any_char.that_fails(lit("\\") | lit('"')),
115
+ escape_sequence,
116
+ )))
117
+ )
118
+ end
119
+ end
120
+
121
+ define_combinator :number do
122
+ ~splicer.start do
123
+ group(
124
+ optional(lit("-") | lit("+")),
125
+ decimal,
126
+ optional(group(lit("."), optional(decimal))),
127
+ ).fmap do |(sign, whole_part, (_, fractional_part))|
128
+ n = whole_part
129
+ n += (fractional_part || 0).to_f / 10 ** fractional_part.to_s.length
130
+ sign == "-" ? -n : n
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,3 @@
1
+ class Parsby
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,42 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "parsby/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "parsby"
8
+ spec.version = Parsby::VERSION
9
+ spec.authors = ["Jorge Luis Martinez Gomez"]
10
+ spec.email = ["jol@jol.dev"]
11
+
12
+ spec.summary = %q{Parser combinator library inspired by Haskell's Parsec}
13
+ #spec.description = %q{TODO: Write a longer description or delete this line.}
14
+ #spec.homepage = "TODO: Put your gem's website or public repo URL here."
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ #if spec.respond_to?(:metadata)
19
+ # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
20
+
21
+ # #spec.metadata["homepage_uri"] = spec.homepage
22
+ # #spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
23
+ # #spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
24
+ #else
25
+ # raise "RubyGems 2.0 or newer is required to protect against " \
26
+ # "public gem pushes."
27
+ #end
28
+
29
+ # Specify which files should be added to the gem when it is released.
30
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
31
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
32
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
33
+ end
34
+ spec.bindir = "exe"
35
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
36
+ spec.require_paths = ["lib"]
37
+
38
+ spec.add_development_dependency "bundler", "~> 1.17"
39
+ spec.add_development_dependency "rake", "~> 10.0"
40
+ spec.add_development_dependency "rspec", "~> 3.0"
41
+ spec.add_development_dependency "pry"
42
+ end
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: parsby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jorge Luis Martinez Gomez
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-09-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.17'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description:
70
+ email:
71
+ - jol@jol.dev
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".rspec"
78
+ - ".ruby-version"
79
+ - ".travis.yml"
80
+ - Gemfile
81
+ - Gemfile.lock
82
+ - README.md
83
+ - Rakefile
84
+ - bin/all-methods
85
+ - bin/console
86
+ - bin/methods-with-pending-documentation
87
+ - bin/setup
88
+ - bin/tested-methods
89
+ - bin/vestigial-methods
90
+ - lib/parsby.rb
91
+ - lib/parsby/combinators.rb
92
+ - lib/parsby/example/arithmetic_parser.rb
93
+ - lib/parsby/example/csv_parser.rb
94
+ - lib/parsby/example/json_parser.rb
95
+ - lib/parsby/example/lisp_parser.rb
96
+ - lib/parsby/version.rb
97
+ - parsby.gemspec
98
+ homepage:
99
+ licenses: []
100
+ metadata: {}
101
+ post_install_message:
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ requirements: []
116
+ rubyforge_project:
117
+ rubygems_version: 2.6.11
118
+ signing_key:
119
+ specification_version: 4
120
+ summary: Parser combinator library inspired by Haskell's Parsec
121
+ test_files: []