rsql_parser 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +289 -0
- data/Rakefile +56 -0
- data/lib/rsql_parser/lexer.rex +54 -0
- data/lib/rsql_parser/lexer.rex.rb +183 -0
- data/lib/rsql_parser/parser.rb +254 -0
- data/lib/rsql_parser/parser.y +63 -0
- data/lib/rsql_parser/version.rb +3 -0
- data/lib/rsql_parser.rb +9 -0
- data/rsql_ruby.gemspec +36 -0
- data/sig/rsql_parser.rbs +14 -0
- metadata +115 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 716f47ee34f3a7940273ea8e80c7455b9ae13be5c93012f149583768ae00c762
|
|
4
|
+
data.tar.gz: bb977f10808ea4b793a702c0e3cd6298de2c700979fe41b08e52577f541a3dcc
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c184118105113d2fe6dbf257498a9ea834b6bb07dee3739e42b3bb25a570e74803a8c462a492ae398fb962c30d6ac173645b333b285098c33e245b0afb45c403
|
|
7
|
+
data.tar.gz: 578825e47740a7e4d731be0f28cc6cfc57cdd0cb18bfacd4f64ec0c29dd464574d8b79f80d027e0c08d9a1d8b24608f4788ae9bc804f1b856313633066e8ed2f
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2010 - 2020 Blue Spire Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# rsql_parser
|
|
2
|
+
|
|
3
|
+
A Ruby parser library for **RSQL** and **FIQL** query expressions. Parses query strings into structured Ruby hashes that can be used to build database queries, filter collections, or power search APIs.
|
|
4
|
+
|
|
5
|
+
- **FIQL** (Feed Item Query Language): [RFC draft](https://datatracker.ietf.org/doc/html/draft-nottingham-atompub-fiql-00)
|
|
6
|
+
- **RSQL**: a superset of FIQL with additional convenience syntax
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
Add to your `Gemfile`:
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
gem 'rsql_parser'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Or install directly:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
gem install rsql_parser
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
require 'rsql_parser'
|
|
26
|
+
|
|
27
|
+
result = RsqlParser.parse('name=="Kill Bill";year=gt=2003')
|
|
28
|
+
# => {
|
|
29
|
+
# type: :COMBINATION,
|
|
30
|
+
# operator: :AND,
|
|
31
|
+
# lhs: { type: :CONSTRAINT, selector: "name", comparison: "==", argument: "Kill Bill" },
|
|
32
|
+
# rhs: { type: :CONSTRAINT, selector: "year", comparison: "=gt=", argument: "2003" }
|
|
33
|
+
# }
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Return Value Structure
|
|
37
|
+
|
|
38
|
+
Every call to `RsqlParser.parse` returns a **node hash**. There are two node types:
|
|
39
|
+
|
|
40
|
+
### `:CONSTRAINT` — a single condition
|
|
41
|
+
|
|
42
|
+
| Key | Type | Description |
|
|
43
|
+
|--------------|----------|------------------------------------|
|
|
44
|
+
| `:type` | Symbol | Always `:CONSTRAINT` |
|
|
45
|
+
| `:selector` | String | The field/attribute name |
|
|
46
|
+
| `:comparison`| String | The comparison operator |
|
|
47
|
+
| `:argument` | String or Array | The value(s) to compare against |
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
RsqlParser.parse('year==2003')
|
|
51
|
+
# => { type: :CONSTRAINT, selector: "year", comparison: "==", argument: "2003" }
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### `:COMBINATION` — two conditions joined by a logical operator
|
|
55
|
+
|
|
56
|
+
| Key | Type | Description |
|
|
57
|
+
|-------------|--------|-------------------------------------|
|
|
58
|
+
| `:type` | Symbol | Always `:COMBINATION` |
|
|
59
|
+
| `:operator` | Symbol | `:AND` or `:OR` |
|
|
60
|
+
| `:lhs` | Hash | Left-hand side node |
|
|
61
|
+
| `:rhs` | Hash | Right-hand side node |
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
RsqlParser.parse('a==1;b==2')
|
|
65
|
+
# => {
|
|
66
|
+
# type: :COMBINATION,
|
|
67
|
+
# operator: :AND,
|
|
68
|
+
# lhs: { type: :CONSTRAINT, selector: "a", comparison: "==", argument: "1" },
|
|
69
|
+
# rhs: { type: :CONSTRAINT, selector: "b", comparison: "==", argument: "2" }
|
|
70
|
+
# }
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Syntax Reference
|
|
74
|
+
|
|
75
|
+
### Selectors
|
|
76
|
+
|
|
77
|
+
A selector is a field name consisting of unreserved characters: letters, digits, and `-._~:`.
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
name
|
|
81
|
+
created_at
|
|
82
|
+
user.email
|
|
83
|
+
http://schema.org/name
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Comparison Operators
|
|
87
|
+
|
|
88
|
+
#### FIQL / RSQL operators
|
|
89
|
+
|
|
90
|
+
| Operator | Meaning | Example |
|
|
91
|
+
|-----------|--------------------------|----------------------|
|
|
92
|
+
| `==` | Equal | `name==Alice` |
|
|
93
|
+
| `!=` | Not equal | `status!=inactive` |
|
|
94
|
+
| `=gt=` | Greater than | `year=gt=2000` |
|
|
95
|
+
| `=gte=` | Greater than or equal | `year=gte=2000` |
|
|
96
|
+
| `=lt=` | Less than | `price=lt=100` |
|
|
97
|
+
| `=lte=` | Less than or equal | `price=lte=100` |
|
|
98
|
+
| `=in=` | In a set | `status=in=(a,b,c)` |
|
|
99
|
+
| `=out=` | Not in a set | `status=out=(x,y)` |
|
|
100
|
+
| `=custom=`| Any custom operator | `field=op=value` |
|
|
101
|
+
|
|
102
|
+
Custom FIQL operators follow the pattern `=[a-z!]*=` — any lowercase letters or `!` between two `=` signs.
|
|
103
|
+
|
|
104
|
+
#### Simplified comparison operators
|
|
105
|
+
|
|
106
|
+
| Operator | Meaning | Example |
|
|
107
|
+
|----------|--------------------------|---------------|
|
|
108
|
+
| `>` | Greater than | `year>2000` |
|
|
109
|
+
| `>=` | Greater than or equal | `year>=2000` |
|
|
110
|
+
| `<` | Less than | `price<100` |
|
|
111
|
+
| `<=` | Less than or equal | `price<=100` |
|
|
112
|
+
|
|
113
|
+
### Logical Operators
|
|
114
|
+
|
|
115
|
+
Conditions can be combined with AND and OR. AND has higher precedence than OR.
|
|
116
|
+
|
|
117
|
+
#### Symbol syntax
|
|
118
|
+
|
|
119
|
+
| Symbol | Operator | Example |
|
|
120
|
+
|--------|----------|-------------------|
|
|
121
|
+
| `;` | AND | `a==1;b==2` |
|
|
122
|
+
| `,` | OR | `a==1,b==2` |
|
|
123
|
+
|
|
124
|
+
#### Keyword syntax (case-insensitive)
|
|
125
|
+
|
|
126
|
+
| Keyword | Operator | Example |
|
|
127
|
+
|---------------|----------|----------------------|
|
|
128
|
+
| `and` / `AND` | AND | `a==1 and b==2` |
|
|
129
|
+
| `or` / `OR` | OR | `a==1 or b==2` |
|
|
130
|
+
|
|
131
|
+
Symbol and keyword syntax can be mixed freely. Whitespace around keywords is ignored.
|
|
132
|
+
|
|
133
|
+
### Arguments
|
|
134
|
+
|
|
135
|
+
#### Unquoted values
|
|
136
|
+
|
|
137
|
+
Sequences of unreserved characters (`[a-zA-Z0-9\-._~:]`):
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
year==2003
|
|
141
|
+
status==active
|
|
142
|
+
date==2018-09-01T12:14:28Z
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
#### Single-quoted strings
|
|
146
|
+
|
|
147
|
+
Allows spaces, semicolons, commas, and double quotes inside the value. Use `\'` to include a literal single quote:
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
name=='Kill;"Bill"'
|
|
151
|
+
tag=='it\'s fine'
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
#### Double-quoted strings
|
|
155
|
+
|
|
156
|
+
Allows spaces, semicolons, commas, and single quotes inside the value. Use `\"` to include a literal double quote:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
name=="Kill Bill"
|
|
160
|
+
title=="She said \"hello\""
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### Array arguments
|
|
164
|
+
|
|
165
|
+
A parenthesised, comma-separated list. Used with operators like `=in=`:
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
status=in=(active,pending,review)
|
|
169
|
+
name=in=("Kill Bill","Pulp Fiction")
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The `:argument` key will contain a Ruby `Array` instead of a `String`:
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
RsqlParser.parse('genre=in=(sci-fi,action)')
|
|
176
|
+
# => { type: :CONSTRAINT, selector: "genre", comparison: "=in=",
|
|
177
|
+
# argument: ["sci-fi", "action"] }
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Grouping
|
|
181
|
+
|
|
182
|
+
Parentheses override the default AND-before-OR precedence:
|
|
183
|
+
|
|
184
|
+
```ruby
|
|
185
|
+
# Without grouping: (a AND b) OR c
|
|
186
|
+
RsqlParser.parse('a==1;b==2,c==3')
|
|
187
|
+
|
|
188
|
+
# With grouping: a AND (b OR c)
|
|
189
|
+
RsqlParser.parse('a==1;(b==2,c==3)')
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Examples
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
require 'rsql_parser'
|
|
196
|
+
|
|
197
|
+
# Single constraint
|
|
198
|
+
RsqlParser.parse('year==2003')
|
|
199
|
+
# => { type: :CONSTRAINT, selector: "year", comparison: "==", argument: "2003" }
|
|
200
|
+
|
|
201
|
+
# Simplified comparison syntax
|
|
202
|
+
RsqlParser.parse('price<=99')
|
|
203
|
+
# => { type: :CONSTRAINT, selector: "price", comparison: "<=", argument: "99" }
|
|
204
|
+
|
|
205
|
+
# AND combination (semicolon and keyword are equivalent)
|
|
206
|
+
RsqlParser.parse('name=="Kill Bill" and year=gt=2003')
|
|
207
|
+
RsqlParser.parse('name=="Kill Bill";year=gt=2003')
|
|
208
|
+
|
|
209
|
+
# OR combination
|
|
210
|
+
RsqlParser.parse('status==active or status==pending')
|
|
211
|
+
RsqlParser.parse('status==active,status==pending')
|
|
212
|
+
|
|
213
|
+
# Array argument
|
|
214
|
+
RsqlParser.parse("genre=in=(sci-fi,action);year>2000")
|
|
215
|
+
# => {
|
|
216
|
+
# type: :COMBINATION,
|
|
217
|
+
# operator: :AND,
|
|
218
|
+
# lhs: { type: :CONSTRAINT, selector: "genre", comparison: "=in=",
|
|
219
|
+
# argument: ["sci-fi", "action"] },
|
|
220
|
+
# rhs: { type: :CONSTRAINT, selector: "year", comparison: ">",
|
|
221
|
+
# argument: "2000" }
|
|
222
|
+
# }
|
|
223
|
+
|
|
224
|
+
# Chained AND — right-associative tree
|
|
225
|
+
RsqlParser.parse('a=eq=b;c=ne=d;e=gt=f')
|
|
226
|
+
# => {
|
|
227
|
+
# type: :COMBINATION, operator: :AND,
|
|
228
|
+
# lhs: { type: :CONSTRAINT, selector: "a", comparison: "=eq=", argument: "b" },
|
|
229
|
+
# rhs: {
|
|
230
|
+
# type: :COMBINATION, operator: :AND,
|
|
231
|
+
# lhs: { type: :CONSTRAINT, selector: "c", comparison: "=ne=", argument: "d" },
|
|
232
|
+
# rhs: { type: :CONSTRAINT, selector: "e", comparison: "=gt=", argument: "f" }
|
|
233
|
+
# }
|
|
234
|
+
# }
|
|
235
|
+
|
|
236
|
+
# Grouping to change precedence
|
|
237
|
+
RsqlParser.parse('a=eq=b;(c=ne=d,e=gt=f)')
|
|
238
|
+
# => {
|
|
239
|
+
# type: :COMBINATION, operator: :AND,
|
|
240
|
+
# lhs: { type: :CONSTRAINT, selector: "a", comparison: "=eq=", argument: "b" },
|
|
241
|
+
# rhs: {
|
|
242
|
+
# type: :COMBINATION, operator: :OR,
|
|
243
|
+
# lhs: { type: :CONSTRAINT, selector: "c", comparison: "=ne=", argument: "d" },
|
|
244
|
+
# rhs: { type: :CONSTRAINT, selector: "e", comparison: "=gt=", argument: "f" }
|
|
245
|
+
# }
|
|
246
|
+
# }
|
|
247
|
+
|
|
248
|
+
# Escaped quotes inside strings
|
|
249
|
+
RsqlParser.parse('title=="She said \"hello\""')
|
|
250
|
+
# => { type: :CONSTRAINT, selector: "title", comparison: "==",
|
|
251
|
+
# argument: 'She said "hello"' }
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Operator Precedence
|
|
255
|
+
|
|
256
|
+
From highest to lowest:
|
|
257
|
+
|
|
258
|
+
1. Parentheses `( )`
|
|
259
|
+
2. AND — `;` or `and`
|
|
260
|
+
3. OR — `,` or `or`
|
|
261
|
+
|
|
262
|
+
## Requirements
|
|
263
|
+
|
|
264
|
+
- Ruby >= 2.7.0
|
|
265
|
+
- [racc](https://github.com/ruby/racc) ~> 1.8
|
|
266
|
+
|
|
267
|
+
## Development
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
# Run tests
|
|
271
|
+
rake test
|
|
272
|
+
|
|
273
|
+
# Regenerate the lexer after editing lib/rsql_parser/lexer.rex
|
|
274
|
+
ruby -roedipus_lex -e "
|
|
275
|
+
lex = OedipusLex.new
|
|
276
|
+
lex.parse_file('lib/rsql_parser/lexer.rex')
|
|
277
|
+
File.write('lib/rsql_parser/lexer.rex.rb', lex.generate)
|
|
278
|
+
"
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Development dependencies: `oedipus_lex ~> 2.6`, `minitest ~> 5.21`, `rake ~> 13.0`.
|
|
282
|
+
|
|
283
|
+
## Contributing
|
|
284
|
+
|
|
285
|
+
Open a pull request with your changes and a corresponding test.
|
|
286
|
+
|
|
287
|
+
## License
|
|
288
|
+
|
|
289
|
+
MIT © [Ekzo](https://github.com/ekzo-dev)
|
data/Rakefile
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
require "rake/testtask"
|
|
2
|
+
|
|
3
|
+
GEMSPEC = Gem::Specification.load("rsql_ruby.gemspec")
|
|
4
|
+
|
|
5
|
+
Rake::TestTask.new(:test) do |t|
|
|
6
|
+
t.libs << "lib"
|
|
7
|
+
t.test_files = FileList["test/**/*_test.rb", "test/**/test_*.rb"]
|
|
8
|
+
t.verbose = false
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
LEX_INPUT = "lib/rsql_parser/lexer.rex"
|
|
12
|
+
LEX_OUTPUT = "lib/rsql_parser/lexer.rex.rb"
|
|
13
|
+
|
|
14
|
+
file LEX_OUTPUT => LEX_INPUT do
|
|
15
|
+
ruby <<~RUBY
|
|
16
|
+
-roedipus_lex -e "
|
|
17
|
+
lex = OedipusLex.new
|
|
18
|
+
lex.parse_file('#{LEX_INPUT}')
|
|
19
|
+
File.write('#{LEX_OUTPUT}', lex.generate)
|
|
20
|
+
"
|
|
21
|
+
RUBY
|
|
22
|
+
puts "Generated #{LEX_OUTPUT}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
desc "Regenerate #{LEX_OUTPUT} from #{LEX_INPUT}"
|
|
26
|
+
task :lexer => LEX_OUTPUT
|
|
27
|
+
|
|
28
|
+
PARSER_INPUT = "lib/rsql_parser/parser.y"
|
|
29
|
+
PARSER_OUTPUT = "lib/rsql_parser/parser.rb"
|
|
30
|
+
|
|
31
|
+
file PARSER_OUTPUT => PARSER_INPUT do
|
|
32
|
+
sh "racc #{PARSER_INPUT} -o #{PARSER_OUTPUT}"
|
|
33
|
+
puts "Generated #{PARSER_OUTPUT}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
desc "Regenerate #{PARSER_OUTPUT} from #{PARSER_INPUT}"
|
|
37
|
+
task :parser => PARSER_OUTPUT
|
|
38
|
+
|
|
39
|
+
desc "Regenerate lexer and parser from their source files"
|
|
40
|
+
task :generate => [:lexer, :parser]
|
|
41
|
+
|
|
42
|
+
GEM_FILE = "pkg/#{GEMSPEC.name}-#{GEMSPEC.version}.gem"
|
|
43
|
+
|
|
44
|
+
directory "pkg"
|
|
45
|
+
|
|
46
|
+
desc "Build #{GEMSPEC.name}-#{GEMSPEC.version}.gem into pkg/"
|
|
47
|
+
task :build => "pkg" do
|
|
48
|
+
sh "gem build rsql_ruby.gemspec --output #{GEM_FILE}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
desc "Build and push #{GEMSPEC.name}-#{GEMSPEC.version}.gem to RubyGems"
|
|
52
|
+
task :release => [:test, :build] do
|
|
53
|
+
sh "gem push #{GEM_FILE}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
task default: :test
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module RsqlParser
|
|
2
|
+
|
|
3
|
+
class Parser < Racc::Parser
|
|
4
|
+
|
|
5
|
+
macro
|
|
6
|
+
BLANK /(\ |\t)+/
|
|
7
|
+
UNRESERVED /[a-zA-Z0-9\-._~:]+/
|
|
8
|
+
COMPARATOR />=|<=|!=|>|<|(=([a-z]|!)*=)/
|
|
9
|
+
|
|
10
|
+
# Operator query
|
|
11
|
+
AND_OPERATOR /;/
|
|
12
|
+
OR_COMMA_OPERATOR /,/
|
|
13
|
+
|
|
14
|
+
# String Parser
|
|
15
|
+
SINGLE_QUOTE /'/
|
|
16
|
+
DOUBLE_QUOTE /"/
|
|
17
|
+
SINGLE_QUOTED_STRING /((\\')|[^'])+/
|
|
18
|
+
DOUBLE_QUOTED_STRING /((\\")|[^"])+/
|
|
19
|
+
|
|
20
|
+
# Brakets
|
|
21
|
+
OPENING_BRACKET /\(/
|
|
22
|
+
CLOSING_BRACKET /\)/
|
|
23
|
+
ESCAPE /\\/
|
|
24
|
+
|
|
25
|
+
rule
|
|
26
|
+
/#{BLANK}/ { }
|
|
27
|
+
/#{ESCAPE}/ { [:ESCAPE, text] }
|
|
28
|
+
/and(?![a-zA-Z0-9\-._~:])/i { [:AND_OPERATOR, text] }
|
|
29
|
+
/or(?![a-zA-Z0-9\-._~:])/i { [:OR_COMMA_OPERATOR, text] }
|
|
30
|
+
/#{UNRESERVED}/ { [:UNRESERVED, text] }
|
|
31
|
+
|
|
32
|
+
/#{COMPARATOR}/ { [:COMPARATOR, text] }
|
|
33
|
+
|
|
34
|
+
/#{OR_COMMA_OPERATOR}/ { [:OR_COMMA_OPERATOR, text] }
|
|
35
|
+
/#{AND_OPERATOR}/ { [:AND_OPERATOR, text] }
|
|
36
|
+
|
|
37
|
+
/#{OPENING_BRACKET}/ { [:OPENING_BRACKET, text] }
|
|
38
|
+
/#{CLOSING_BRACKET}/ { [:CLOSING_BRACKET, text] }
|
|
39
|
+
|
|
40
|
+
/#{SINGLE_QUOTE}/ { self.state = :SINGLE_QUOTE; [:SINGLE_QUOTE, text] }
|
|
41
|
+
:SINGLE_QUOTE /#{SINGLE_QUOTED_STRING}/ { [:SINGLE_QUOTED_STRING, text.gsub("\\'", "'")] }
|
|
42
|
+
:SINGLE_QUOTE /#{SINGLE_QUOTE}/ { self.state = nil; [:SINGLE_QUOTE, text] }
|
|
43
|
+
|
|
44
|
+
/#{DOUBLE_QUOTE}/ { self.state = :DOUBLE_QUOTE; [:DOUBLE_QUOTE, text] }
|
|
45
|
+
:DOUBLE_QUOTE /#{DOUBLE_QUOTED_STRING}/ { [:DOUBLE_QUOTED_STRING, text.gsub("\\\"", '"')] }
|
|
46
|
+
:DOUBLE_QUOTE /#{DOUBLE_QUOTE}/ { self.state = nil; [:DOUBLE_QUOTE, text] }
|
|
47
|
+
|
|
48
|
+
option
|
|
49
|
+
|
|
50
|
+
inner
|
|
51
|
+
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
end
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# encoding: UTF-8
|
|
3
|
+
#--
|
|
4
|
+
# This file is automatically generated. Do not modify it.
|
|
5
|
+
# Generated by: oedipus_lex version 2.6.3.
|
|
6
|
+
# Source: lib/rsql_parser/lexer.rex
|
|
7
|
+
#++
|
|
8
|
+
|
|
9
|
+
module RsqlParser
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
##
|
|
13
|
+
# The generated lexer Parser < Racc::Parser
|
|
14
|
+
|
|
15
|
+
class Parser < Racc::Parser
|
|
16
|
+
require 'strscan'
|
|
17
|
+
|
|
18
|
+
# :stopdoc:
|
|
19
|
+
BLANK = /(\ |\t)+/
|
|
20
|
+
UNRESERVED = /[a-zA-Z0-9\-._~:]+/
|
|
21
|
+
COMPARATOR = />=|<=|!=|>|<|(=([a-z]|!)*=)/
|
|
22
|
+
AND_OPERATOR = /;/
|
|
23
|
+
OR_COMMA_OPERATOR = /,/
|
|
24
|
+
SINGLE_QUOTE = /'/
|
|
25
|
+
DOUBLE_QUOTE = /"/
|
|
26
|
+
SINGLE_QUOTED_STRING = /((\\')|[^'])+/
|
|
27
|
+
DOUBLE_QUOTED_STRING = /((\\")|[^"])+/
|
|
28
|
+
OPENING_BRACKET = /\(/
|
|
29
|
+
CLOSING_BRACKET = /\)/
|
|
30
|
+
ESCAPE = /\\/
|
|
31
|
+
# :startdoc:
|
|
32
|
+
# :stopdoc:
|
|
33
|
+
class LexerError < StandardError ; end
|
|
34
|
+
class ScanError < LexerError ; end
|
|
35
|
+
# :startdoc:
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# The file name / path
|
|
39
|
+
|
|
40
|
+
attr_accessor :filename
|
|
41
|
+
|
|
42
|
+
##
|
|
43
|
+
# The StringScanner for this lexer.
|
|
44
|
+
|
|
45
|
+
attr_accessor :ss
|
|
46
|
+
|
|
47
|
+
##
|
|
48
|
+
# The current lexical state.
|
|
49
|
+
|
|
50
|
+
attr_accessor :state
|
|
51
|
+
|
|
52
|
+
alias :match :ss
|
|
53
|
+
|
|
54
|
+
##
|
|
55
|
+
# The match groups for the current scan.
|
|
56
|
+
|
|
57
|
+
def matches
|
|
58
|
+
m = (1..9).map { |i| ss[i] }
|
|
59
|
+
m.pop until m[-1] or m.empty?
|
|
60
|
+
m
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
##
|
|
64
|
+
# Yields on the current action.
|
|
65
|
+
|
|
66
|
+
def action
|
|
67
|
+
yield
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
##
|
|
71
|
+
# The current scanner class. Must be overridden in subclasses.
|
|
72
|
+
|
|
73
|
+
def scanner_class
|
|
74
|
+
StringScanner
|
|
75
|
+
end unless instance_methods(false).map(&:to_s).include?("scanner_class")
|
|
76
|
+
|
|
77
|
+
##
|
|
78
|
+
# Parse the given string.
|
|
79
|
+
|
|
80
|
+
def parse str
|
|
81
|
+
self.ss = scanner_class.new str
|
|
82
|
+
self.state ||= nil
|
|
83
|
+
|
|
84
|
+
do_parse
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
##
|
|
88
|
+
# Read in and parse the file at +path+.
|
|
89
|
+
|
|
90
|
+
def parse_file path
|
|
91
|
+
self.filename = path
|
|
92
|
+
open path do |f|
|
|
93
|
+
parse f.read
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
##
|
|
98
|
+
# The current location in the parse.
|
|
99
|
+
|
|
100
|
+
def location
|
|
101
|
+
[
|
|
102
|
+
(filename || "<input>"),
|
|
103
|
+
].compact.join(":")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
##
|
|
107
|
+
# Lex the next token.
|
|
108
|
+
|
|
109
|
+
def next_token
|
|
110
|
+
|
|
111
|
+
token = nil
|
|
112
|
+
|
|
113
|
+
until ss.eos? or token do
|
|
114
|
+
token =
|
|
115
|
+
case state
|
|
116
|
+
when nil then
|
|
117
|
+
case
|
|
118
|
+
when ss.skip(/#{BLANK}/) then
|
|
119
|
+
action { }
|
|
120
|
+
when text = ss.scan(/#{ESCAPE}/) then
|
|
121
|
+
action { [:ESCAPE, text] }
|
|
122
|
+
when text = ss.scan(/and(?![a-zA-Z0-9\-._~:])/i) then
|
|
123
|
+
action { [:AND_OPERATOR, text] }
|
|
124
|
+
when text = ss.scan(/or(?![a-zA-Z0-9\-._~:])/i) then
|
|
125
|
+
action { [:OR_COMMA_OPERATOR, text] }
|
|
126
|
+
when text = ss.scan(/#{UNRESERVED}/) then
|
|
127
|
+
action { [:UNRESERVED, text] }
|
|
128
|
+
when text = ss.scan(/#{COMPARATOR}/) then
|
|
129
|
+
action { [:COMPARATOR, text] }
|
|
130
|
+
when text = ss.scan(/#{OR_COMMA_OPERATOR}/) then
|
|
131
|
+
action { [:OR_COMMA_OPERATOR, text] }
|
|
132
|
+
when text = ss.scan(/#{AND_OPERATOR}/) then
|
|
133
|
+
action { [:AND_OPERATOR, text] }
|
|
134
|
+
when text = ss.scan(/#{OPENING_BRACKET}/) then
|
|
135
|
+
action { [:OPENING_BRACKET, text] }
|
|
136
|
+
when text = ss.scan(/#{CLOSING_BRACKET}/) then
|
|
137
|
+
action { [:CLOSING_BRACKET, text] }
|
|
138
|
+
when text = ss.scan(/#{SINGLE_QUOTE}/) then
|
|
139
|
+
action { self.state = :SINGLE_QUOTE; [:SINGLE_QUOTE, text] }
|
|
140
|
+
when text = ss.scan(/#{DOUBLE_QUOTE}/) then
|
|
141
|
+
action { self.state = :DOUBLE_QUOTE; [:DOUBLE_QUOTE, text] }
|
|
142
|
+
else
|
|
143
|
+
text = ss.string[ss.pos .. -1]
|
|
144
|
+
raise ScanError, "can not match (#{state.inspect}) at #{location}: '#{text}'"
|
|
145
|
+
end
|
|
146
|
+
when :SINGLE_QUOTE then
|
|
147
|
+
case
|
|
148
|
+
when text = ss.scan(/#{SINGLE_QUOTED_STRING}/) then
|
|
149
|
+
action { [:SINGLE_QUOTED_STRING, text.gsub("\\'", "'")] }
|
|
150
|
+
when text = ss.scan(/#{SINGLE_QUOTE}/) then
|
|
151
|
+
action { self.state = nil; [:SINGLE_QUOTE, text] }
|
|
152
|
+
else
|
|
153
|
+
text = ss.string[ss.pos .. -1]
|
|
154
|
+
raise ScanError, "can not match (#{state.inspect}) at #{location}: '#{text}'"
|
|
155
|
+
end
|
|
156
|
+
when :DOUBLE_QUOTE then
|
|
157
|
+
case
|
|
158
|
+
when text = ss.scan(/#{DOUBLE_QUOTED_STRING}/) then
|
|
159
|
+
action { [:DOUBLE_QUOTED_STRING, text.gsub("\\\"", '"')] }
|
|
160
|
+
when text = ss.scan(/#{DOUBLE_QUOTE}/) then
|
|
161
|
+
action { self.state = nil; [:DOUBLE_QUOTE, text] }
|
|
162
|
+
else
|
|
163
|
+
text = ss.string[ss.pos .. -1]
|
|
164
|
+
raise ScanError, "can not match (#{state.inspect}) at #{location}: '#{text}'"
|
|
165
|
+
end
|
|
166
|
+
else
|
|
167
|
+
raise ScanError, "undefined state at #{location}: '#{state}'"
|
|
168
|
+
end # token = case state
|
|
169
|
+
|
|
170
|
+
next unless token # allow functions to trigger redo w/ nil
|
|
171
|
+
end # while
|
|
172
|
+
|
|
173
|
+
raise LexerError, "bad lexical result at #{location}: #{token.inspect}" unless
|
|
174
|
+
token.nil? || (Array === token && token.size >= 2)
|
|
175
|
+
|
|
176
|
+
# auto-switch state
|
|
177
|
+
self.state = token.last if token && token.first == :state
|
|
178
|
+
|
|
179
|
+
token
|
|
180
|
+
end # def next_token
|
|
181
|
+
end # class
|
|
182
|
+
|
|
183
|
+
end
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
#
|
|
2
|
+
# DO NOT MODIFY!!!!
|
|
3
|
+
# This file is automatically generated by Racc 1.8.1
|
|
4
|
+
# from Racc grammar file "parser.y".
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
require 'racc/parser.rb'
|
|
8
|
+
|
|
9
|
+
# Generated by racc
|
|
10
|
+
require_relative 'lexer.rex'
|
|
11
|
+
|
|
12
|
+
module RsqlParser
|
|
13
|
+
class Parser < Racc::Parser
|
|
14
|
+
|
|
15
|
+
module_eval(<<'...end parser.y/module_eval...', 'parser.y', 44)
|
|
16
|
+
|
|
17
|
+
def create_logical_operator(operator, lhs, rhs)
|
|
18
|
+
{
|
|
19
|
+
type: :COMBINATION,
|
|
20
|
+
operator: operator,
|
|
21
|
+
lhs: lhs,
|
|
22
|
+
rhs: rhs
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def create_constraint(selector, comparison, argument)
|
|
27
|
+
{
|
|
28
|
+
type: :CONSTRAINT,
|
|
29
|
+
selector: selector,
|
|
30
|
+
comparison: comparison,
|
|
31
|
+
argument: argument
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
...end parser.y/module_eval...
|
|
36
|
+
##### State transition tables begin ###
|
|
37
|
+
|
|
38
|
+
racc_action_table = [
|
|
39
|
+
19, 8, 20, 21, 9, 22, 25, 20, 21, 30,
|
|
40
|
+
22, 20, 21, 29, 22, 6, 6, 7, 7, 6,
|
|
41
|
+
6, 7, 7, 10, 11, 13, 23, 27, 28, 31,
|
|
42
|
+
32 ]
|
|
43
|
+
|
|
44
|
+
racc_action_check = [
|
|
45
|
+
11, 1, 11, 11, 3, 11, 19, 19, 19, 24,
|
|
46
|
+
19, 30, 30, 24, 30, 0, 6, 0, 6, 9,
|
|
47
|
+
10, 9, 10, 4, 5, 8, 12, 21, 22, 27,
|
|
48
|
+
28 ]
|
|
49
|
+
|
|
50
|
+
racc_action_pointer = [
|
|
51
|
+
8, 1, nil, 0, 18, 18, 9, nil, 25, 12,
|
|
52
|
+
13, -7, 18, nil, nil, nil, nil, nil, nil, -2,
|
|
53
|
+
nil, 16, 15, nil, 5, nil, nil, 19, 18, nil,
|
|
54
|
+
2, nil, nil, nil ]
|
|
55
|
+
|
|
56
|
+
racc_action_default = [
|
|
57
|
+
-19, -19, -1, -3, -5, -7, -19, -9, -19, -19,
|
|
58
|
+
-19, -19, -19, 34, -2, -4, -6, -10, -11, -19,
|
|
59
|
+
-16, -19, -19, -8, -19, -13, -14, -19, -19, -12,
|
|
60
|
+
-19, -17, -18, -15 ]
|
|
61
|
+
|
|
62
|
+
racc_goto_table = [
|
|
63
|
+
18, 2, 1, 15, 16, 17, 24, 12, 26, nil,
|
|
64
|
+
14, nil, nil, nil, nil, nil, nil, nil, nil, 33 ]
|
|
65
|
+
|
|
66
|
+
racc_goto_check = [
|
|
67
|
+
8, 2, 1, 3, 6, 7, 9, 2, 8, nil,
|
|
68
|
+
2, nil, nil, nil, nil, nil, nil, nil, nil, 8 ]
|
|
69
|
+
|
|
70
|
+
racc_goto_pointer = [
|
|
71
|
+
nil, 2, 1, -7, nil, nil, -7, -6, -11, -13 ]
|
|
72
|
+
|
|
73
|
+
racc_goto_default = [
|
|
74
|
+
nil, nil, nil, 3, 4, 5, nil, nil, nil, nil ]
|
|
75
|
+
|
|
76
|
+
racc_reduce_table = [
|
|
77
|
+
0, 0, :racc_error,
|
|
78
|
+
1, 15, :_reduce_none,
|
|
79
|
+
3, 16, :_reduce_2,
|
|
80
|
+
1, 16, :_reduce_none,
|
|
81
|
+
3, 17, :_reduce_4,
|
|
82
|
+
1, 17, :_reduce_none,
|
|
83
|
+
3, 18, :_reduce_6,
|
|
84
|
+
1, 18, :_reduce_none,
|
|
85
|
+
3, 18, :_reduce_8,
|
|
86
|
+
1, 19, :_reduce_none,
|
|
87
|
+
1, 20, :_reduce_none,
|
|
88
|
+
1, 20, :_reduce_none,
|
|
89
|
+
3, 21, :_reduce_12,
|
|
90
|
+
2, 21, :_reduce_13,
|
|
91
|
+
1, 23, :_reduce_14,
|
|
92
|
+
3, 23, :_reduce_15,
|
|
93
|
+
1, 22, :_reduce_none,
|
|
94
|
+
3, 22, :_reduce_17,
|
|
95
|
+
3, 22, :_reduce_18 ]
|
|
96
|
+
|
|
97
|
+
racc_reduce_n = 19
|
|
98
|
+
|
|
99
|
+
racc_shift_n = 34
|
|
100
|
+
|
|
101
|
+
racc_token_table = {
|
|
102
|
+
false => 0,
|
|
103
|
+
:error => 1,
|
|
104
|
+
";" => 2,
|
|
105
|
+
"," => 3,
|
|
106
|
+
:OR_COMMA_OPERATOR => 4,
|
|
107
|
+
:AND_OPERATOR => 5,
|
|
108
|
+
:COMPARATOR => 6,
|
|
109
|
+
:OPENING_BRACKET => 7,
|
|
110
|
+
:CLOSING_BRACKET => 8,
|
|
111
|
+
:UNRESERVED => 9,
|
|
112
|
+
:SINGLE_QUOTE => 10,
|
|
113
|
+
:SINGLE_QUOTED_STRING => 11,
|
|
114
|
+
:DOUBLE_QUOTE => 12,
|
|
115
|
+
:DOUBLE_QUOTED_STRING => 13 }
|
|
116
|
+
|
|
117
|
+
racc_nt_base = 14
|
|
118
|
+
|
|
119
|
+
racc_use_result_var = false
|
|
120
|
+
|
|
121
|
+
Racc_arg = [
|
|
122
|
+
racc_action_table,
|
|
123
|
+
racc_action_check,
|
|
124
|
+
racc_action_default,
|
|
125
|
+
racc_action_pointer,
|
|
126
|
+
racc_goto_table,
|
|
127
|
+
racc_goto_check,
|
|
128
|
+
racc_goto_default,
|
|
129
|
+
racc_goto_pointer,
|
|
130
|
+
racc_nt_base,
|
|
131
|
+
racc_reduce_table,
|
|
132
|
+
racc_token_table,
|
|
133
|
+
racc_shift_n,
|
|
134
|
+
racc_reduce_n,
|
|
135
|
+
racc_use_result_var ]
|
|
136
|
+
Ractor.make_shareable(Racc_arg) if defined?(Ractor)
|
|
137
|
+
|
|
138
|
+
Racc_token_to_s_table = [
|
|
139
|
+
"$end",
|
|
140
|
+
"error",
|
|
141
|
+
"\";\"",
|
|
142
|
+
"\",\"",
|
|
143
|
+
"OR_COMMA_OPERATOR",
|
|
144
|
+
"AND_OPERATOR",
|
|
145
|
+
"COMPARATOR",
|
|
146
|
+
"OPENING_BRACKET",
|
|
147
|
+
"CLOSING_BRACKET",
|
|
148
|
+
"UNRESERVED",
|
|
149
|
+
"SINGLE_QUOTE",
|
|
150
|
+
"SINGLE_QUOTED_STRING",
|
|
151
|
+
"DOUBLE_QUOTE",
|
|
152
|
+
"DOUBLE_QUOTED_STRING",
|
|
153
|
+
"$start",
|
|
154
|
+
"target",
|
|
155
|
+
"disjunction",
|
|
156
|
+
"conjuction",
|
|
157
|
+
"constraint",
|
|
158
|
+
"selector",
|
|
159
|
+
"argument",
|
|
160
|
+
"array",
|
|
161
|
+
"value",
|
|
162
|
+
"contents" ]
|
|
163
|
+
Ractor.make_shareable(Racc_token_to_s_table) if defined?(Ractor)
|
|
164
|
+
|
|
165
|
+
Racc_debug_parser = false
|
|
166
|
+
|
|
167
|
+
##### State transition tables end #####
|
|
168
|
+
|
|
169
|
+
# reduce 0 omitted
|
|
170
|
+
|
|
171
|
+
# reduce 1 omitted
|
|
172
|
+
|
|
173
|
+
module_eval(<<'.,.,', 'parser.y', 12)
|
|
174
|
+
def _reduce_2(val, _values)
|
|
175
|
+
create_logical_operator(:OR, val[0], val[2])
|
|
176
|
+
end
|
|
177
|
+
.,.,
|
|
178
|
+
|
|
179
|
+
# reduce 3 omitted
|
|
180
|
+
|
|
181
|
+
module_eval(<<'.,.,', 'parser.y', 15)
|
|
182
|
+
def _reduce_4(val, _values)
|
|
183
|
+
create_logical_operator(:AND, val[0], val[2])
|
|
184
|
+
end
|
|
185
|
+
.,.,
|
|
186
|
+
|
|
187
|
+
# reduce 5 omitted
|
|
188
|
+
|
|
189
|
+
module_eval(<<'.,.,', 'parser.y', 18)
|
|
190
|
+
def _reduce_6(val, _values)
|
|
191
|
+
create_constraint(val[0], val[1], val[2])
|
|
192
|
+
end
|
|
193
|
+
.,.,
|
|
194
|
+
|
|
195
|
+
# reduce 7 omitted
|
|
196
|
+
|
|
197
|
+
module_eval(<<'.,.,', 'parser.y', 20)
|
|
198
|
+
def _reduce_8(val, _values)
|
|
199
|
+
val[1]
|
|
200
|
+
end
|
|
201
|
+
.,.,
|
|
202
|
+
|
|
203
|
+
# reduce 9 omitted
|
|
204
|
+
|
|
205
|
+
# reduce 10 omitted
|
|
206
|
+
|
|
207
|
+
# reduce 11 omitted
|
|
208
|
+
|
|
209
|
+
module_eval(<<'.,.,', 'parser.y', 27)
|
|
210
|
+
def _reduce_12(val, _values)
|
|
211
|
+
val[1]
|
|
212
|
+
end
|
|
213
|
+
.,.,
|
|
214
|
+
|
|
215
|
+
module_eval(<<'.,.,', 'parser.y', 28)
|
|
216
|
+
def _reduce_13(val, _values)
|
|
217
|
+
[]
|
|
218
|
+
end
|
|
219
|
+
.,.,
|
|
220
|
+
|
|
221
|
+
module_eval(<<'.,.,', 'parser.y', 30)
|
|
222
|
+
def _reduce_14(val, _values)
|
|
223
|
+
[val[0]]
|
|
224
|
+
end
|
|
225
|
+
.,.,
|
|
226
|
+
|
|
227
|
+
module_eval(<<'.,.,', 'parser.y', 31)
|
|
228
|
+
def _reduce_15(val, _values)
|
|
229
|
+
val[0].push(val[2]); val[0]
|
|
230
|
+
end
|
|
231
|
+
.,.,
|
|
232
|
+
|
|
233
|
+
# reduce 16 omitted
|
|
234
|
+
|
|
235
|
+
module_eval(<<'.,.,', 'parser.y', 34)
|
|
236
|
+
def _reduce_17(val, _values)
|
|
237
|
+
val[1]
|
|
238
|
+
end
|
|
239
|
+
.,.,
|
|
240
|
+
|
|
241
|
+
module_eval(<<'.,.,', 'parser.y', 35)
|
|
242
|
+
def _reduce_18(val, _values)
|
|
243
|
+
val[1]
|
|
244
|
+
end
|
|
245
|
+
.,.,
|
|
246
|
+
|
|
247
|
+
def _reduce_none(val, _values)
|
|
248
|
+
val[0]
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
end # class Parser
|
|
252
|
+
end # module RsqlParser
|
|
253
|
+
|
|
254
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# RSQL parser
|
|
2
|
+
|
|
3
|
+
class RsqlParser::Parser
|
|
4
|
+
prechigh
|
|
5
|
+
left ';'
|
|
6
|
+
left ','
|
|
7
|
+
preclow
|
|
8
|
+
options
|
|
9
|
+
no_result_var
|
|
10
|
+
rule
|
|
11
|
+
target : disjunction
|
|
12
|
+
|
|
13
|
+
disjunction : conjuction OR_COMMA_OPERATOR disjunction { create_logical_operator(:OR, val[0], val[2]) }
|
|
14
|
+
| conjuction
|
|
15
|
+
|
|
16
|
+
conjuction : constraint AND_OPERATOR conjuction { create_logical_operator(:AND, val[0], val[2]) }
|
|
17
|
+
| constraint
|
|
18
|
+
|
|
19
|
+
constraint : selector COMPARATOR argument { create_constraint(val[0], val[1], val[2]) }
|
|
20
|
+
| selector
|
|
21
|
+
| OPENING_BRACKET disjunction CLOSING_BRACKET { val[1] }
|
|
22
|
+
|
|
23
|
+
selector : UNRESERVED
|
|
24
|
+
|
|
25
|
+
argument : array
|
|
26
|
+
| value
|
|
27
|
+
|
|
28
|
+
array : OPENING_BRACKET contents CLOSING_BRACKET { val[1] }
|
|
29
|
+
| OPENING_BRACKET CLOSING_BRACKET { [] }
|
|
30
|
+
|
|
31
|
+
contents : value { [val[0]] }
|
|
32
|
+
| contents OR_COMMA_OPERATOR value { val[0].push(val[2]); val[0] }
|
|
33
|
+
|
|
34
|
+
value : UNRESERVED
|
|
35
|
+
| SINGLE_QUOTE SINGLE_QUOTED_STRING SINGLE_QUOTE { val[1] }
|
|
36
|
+
| DOUBLE_QUOTE DOUBLE_QUOTED_STRING DOUBLE_QUOTE { val[1] }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
---- header
|
|
40
|
+
# Generated by racc
|
|
41
|
+
require_relative 'lexer.rex'
|
|
42
|
+
|
|
43
|
+
---- inner
|
|
44
|
+
|
|
45
|
+
def create_logical_operator(operator, lhs, rhs)
|
|
46
|
+
{
|
|
47
|
+
type: :COMBINATION,
|
|
48
|
+
operator: operator,
|
|
49
|
+
lhs: lhs,
|
|
50
|
+
rhs: rhs
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def create_constraint(selector, comparison, argument)
|
|
55
|
+
{
|
|
56
|
+
type: :CONSTRAINT,
|
|
57
|
+
selector: selector,
|
|
58
|
+
comparison: comparison,
|
|
59
|
+
argument: argument
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
---- footer
|
data/lib/rsql_parser.rb
ADDED
data/rsql_ruby.gemspec
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative 'lib/rsql_parser/version'
|
|
3
|
+
|
|
4
|
+
Gem::Specification.new do |spec|
|
|
5
|
+
spec.name = 'rsql_parser'
|
|
6
|
+
spec.version = RsqlParser::VERSION
|
|
7
|
+
spec.authors = ['Dmitry Arkhipov']
|
|
8
|
+
spec.email = ['d.arkhipov@ekzo.dev']
|
|
9
|
+
|
|
10
|
+
spec.summary = 'RSQL/FIQL parser for Ruby'
|
|
11
|
+
spec.description = 'Parse RSQL/FIQL string expression into AST nodes'
|
|
12
|
+
|
|
13
|
+
spec.homepage = 'https://github.com/ekzo-dev/ruby-rsql'
|
|
14
|
+
spec.license = 'MIT'
|
|
15
|
+
spec.required_ruby_version = '>= 2.7.0'
|
|
16
|
+
|
|
17
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
18
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
|
19
|
+
spec.metadata['changelog_uri'] = 'https://github.com/ekzo-dev/ruby-rsql/CHANGELOG.md'
|
|
20
|
+
|
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
23
|
+
spec.files = Dir.chdir(__dir__) do
|
|
24
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
25
|
+
(File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor])
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
spec.bindir = 'exe'
|
|
29
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
30
|
+
spec.require_paths = ['lib']
|
|
31
|
+
|
|
32
|
+
spec.add_dependency 'racc', '~> 1.8'
|
|
33
|
+
spec.add_development_dependency 'oedipus_lex', '~> 2.6'
|
|
34
|
+
spec.add_development_dependency 'minitest', '~> 5.21'
|
|
35
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
|
36
|
+
end
|
data/sig/rsql_parser.rbs
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# RSQL parsed AST
|
|
2
|
+
type rsql_ast = {
|
|
3
|
+
type: :COMBINATION | :CONSTRAINT,
|
|
4
|
+
?lhs: rsql_ast,
|
|
5
|
+
?rhs: rsql_ast,
|
|
6
|
+
?selector: String,
|
|
7
|
+
?comparison: String,
|
|
8
|
+
?argument: String,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module RsqlParser
|
|
12
|
+
# Parse string to AST nodes
|
|
13
|
+
def self.parse: (String str) -> rsql_ast
|
|
14
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rsql_parser
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Dmitry Arkhipov
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-03-27 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: racc
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.8'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.8'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: oedipus_lex
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '2.6'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '2.6'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: minitest
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '5.21'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '5.21'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rake
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '13.0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '13.0'
|
|
69
|
+
description: Parse RSQL/FIQL string expression into AST nodes
|
|
70
|
+
email:
|
|
71
|
+
- d.arkhipov@ekzo.dev
|
|
72
|
+
executables: []
|
|
73
|
+
extensions: []
|
|
74
|
+
extra_rdoc_files: []
|
|
75
|
+
files:
|
|
76
|
+
- CHANGELOG.md
|
|
77
|
+
- Gemfile
|
|
78
|
+
- LICENSE
|
|
79
|
+
- README.md
|
|
80
|
+
- Rakefile
|
|
81
|
+
- lib/rsql_parser.rb
|
|
82
|
+
- lib/rsql_parser/lexer.rex
|
|
83
|
+
- lib/rsql_parser/lexer.rex.rb
|
|
84
|
+
- lib/rsql_parser/parser.rb
|
|
85
|
+
- lib/rsql_parser/parser.y
|
|
86
|
+
- lib/rsql_parser/version.rb
|
|
87
|
+
- rsql_ruby.gemspec
|
|
88
|
+
- sig/rsql_parser.rbs
|
|
89
|
+
homepage: https://github.com/ekzo-dev/ruby-rsql
|
|
90
|
+
licenses:
|
|
91
|
+
- MIT
|
|
92
|
+
metadata:
|
|
93
|
+
homepage_uri: https://github.com/ekzo-dev/ruby-rsql
|
|
94
|
+
source_code_uri: https://github.com/ekzo-dev/ruby-rsql
|
|
95
|
+
changelog_uri: https://github.com/ekzo-dev/ruby-rsql/CHANGELOG.md
|
|
96
|
+
post_install_message:
|
|
97
|
+
rdoc_options: []
|
|
98
|
+
require_paths:
|
|
99
|
+
- lib
|
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
101
|
+
requirements:
|
|
102
|
+
- - ">="
|
|
103
|
+
- !ruby/object:Gem::Version
|
|
104
|
+
version: 2.7.0
|
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0'
|
|
110
|
+
requirements: []
|
|
111
|
+
rubygems_version: 3.5.22
|
|
112
|
+
signing_key:
|
|
113
|
+
specification_version: 4
|
|
114
|
+
summary: RSQL/FIQL parser for Ruby
|
|
115
|
+
test_files: []
|