fop_lang 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b5f19a543b81c0046dc63fcc1c0769989628017d2c1d1da74ef0db9866a0f2f7
4
- data.tar.gz: 03b6597f9cab97c95ccda8396693bb43d9da729137cb916cc74f7fbecc314b32
3
+ metadata.gz: 8ed95bb4708820a186e6485cc29dbb47286b0f309a1caf91af7778d768b0efb3
4
+ data.tar.gz: 85d41728ddae13f3667f0a2d55a5c4dcbc26e8217d4f31466e6ed92038859881
5
5
  SHA512:
6
- metadata.gz: 3a17c82a561e20cbc5cb8abbad5be4f94f02110d60b6130e3e1e9489672c5c134befc6b1daca2f590f083a67934e600fb5d6fa0ea5433181ba3014514c558232
7
- data.tar.gz: 790250c8a79dcf04b381f2dd33cbaa048fd070688ab45446ff87652dcb18844c2d6139d0ead060fa338a57b8590eee0167ea2c25abd84e1d71571f33c49bcbda
6
+ metadata.gz: e650bdf66d8d0b5dcb603eae494f38d4969a19647053b4e81e0612705f5b16a5755d007e4ce01fad9b487224d66eff462738f1e37b011ba2a4bf4a45b0203bb3
7
+ data.tar.gz: 99b31736236785cecc85b9bb23ccc3e366713cd23dd04c992a8fff6676a316d5741bfe5058e17cd0812c43d8bf1979aa7546184386018c1f92ed2462a85eb5fb
data/README.md CHANGED
@@ -29,7 +29,7 @@ The above expression contains the only two parts of Fop (except for the wildcard
29
29
 
30
30
  **Text Literals**
31
31
 
32
- A text literal works how it sounds: the input must match it exactly. The only exception is the `*` (wildcard) character, which matches 0 or more of anything. Wildcards can be used anywhere except inside `{...}` (operations).
32
+ A text literal works how it sounds: the input must match it exactly. If it matches it passes through unchanged. The only exception is the `*` (wildcard) character, which matches 0 or more of anything. Wildcards can be used anywhere except inside `{...}` (operations).
33
33
 
34
34
  If `\` (escape) is used before the special characters `*`, `{` or `}`, then that character is treated like a text literal. It's recommended to use single-quoted Ruby strings with Fop expressions that so you don't need to double-escape.
35
35
 
@@ -42,9 +42,11 @@ Operations are the interesting part of Fop, and are specified between `{` and `}
42
42
  * `A` is the alpha class and will match one or more letters (lower or upper case).
43
43
  * `W` is the word class and matches alphanumeric chars and underscores.
44
44
  * `*` is the wildcard class and greedily matches everything after it.
45
- * `/.../` matches on the supplied regex between the `/`'s. If you're regex contains a `/`, it must be escaped.
45
+ * `/.../` matches on the supplied regex between the `/`'s. If you're regex contains a `/`, it must be escaped. Capture groups may be referenced in the operator argument as `$1`, `$2`, etc.
46
46
  3. Operator (optional): What to do to the matching characters.
47
- * `=` Replace the matching character(s) with the given argument. If no argument is given, drop the matching chars. Note that any `/` chars must be escaped, so as not to be mistaken for a regex.
47
+ * `=` Replace the matching character(s) with the given argument. If no argument is given, drop the matching chars.
48
+ * `>` Append the following chars to the matching value.
49
+ * `<` Prepend the following chars to the matching value.
48
50
  * `+` Perform addition on the matching number and the argument (`N` only).
49
51
  * `-` Subtract the argument from the matching number (`N` only).
50
52
  5. Operator argument (required for some operators): meaning varies by operator.
data/lib/fop/nodes.rb CHANGED
@@ -12,11 +12,15 @@ module Fop
12
12
  end
13
13
  end
14
14
 
15
- Op = Struct.new(:wildcard, :match, :regex_match, :regex, :operator, :operator_arg, :expression) do
15
+ Op = Struct.new(:wildcard, :match, :regex_match, :regex, :operator, :operator_arg, :operator_arg_w_caps, :expression) do
16
16
  def consume!(input)
17
- if (val = input.slice!(regex))
18
- found_val = regex_match || val != Parser::BLANK
19
- expression && found_val ? expression.call(val) : val
17
+ if (match = regex.match(input))
18
+ val = match.to_s
19
+ blank = val == Parser::BLANK
20
+ input.sub!(val, Parser::BLANK) unless blank
21
+ found_val = regex_match || !blank
22
+ arg = operator_arg_w_caps ? sub_caps(operator_arg_w_caps, match.captures) : operator_arg
23
+ expression && found_val ? expression.call(val, operator, arg) : val
20
24
  end
21
25
  end
22
26
 
@@ -26,6 +30,18 @@ module Fop
26
30
  s << " #{operator} #{operator_arg}" if operator
27
31
  s
28
32
  end
33
+
34
+ private
35
+
36
+ def sub_caps(tokens, caps)
37
+ tokens.map { |t|
38
+ case t
39
+ when String then t
40
+ when Parser::CaptureGroup then caps[t.index].to_s
41
+ else raise Parser::Error, "Unexpected #{t} in capture group"
42
+ end
43
+ }.join("")
44
+ end
29
45
  end
30
46
  end
31
47
  end
data/lib/fop/parser.rb CHANGED
@@ -3,6 +3,7 @@ require_relative 'nodes'
3
3
  module Fop
4
4
  module Parser
5
5
  Error = Class.new(StandardError)
6
+ CaptureGroup = Struct.new(:index)
6
7
 
7
8
  MATCH_NUM = "N".freeze
8
9
  MATCH_WORD = "W".freeze
@@ -10,10 +11,19 @@ module Fop
10
11
  MATCH_WILD = "*".freeze
11
12
  BLANK = "".freeze
12
13
  OP_REPLACE = "=".freeze
14
+ OP_APPEND = ">".freeze
15
+ OP_PREPEND = "<".freeze
13
16
  OP_ADD = "+".freeze
14
17
  OP_SUB = "-".freeze
15
18
  OP_MUL = "*".freeze
16
19
  OP_DIV = "/".freeze
20
+ VAR = "$".freeze
21
+ CAP_NUM = /^[1-9]$/
22
+
23
+ EXP_REPLACE = ->(_val, _op, arg) { arg || BLANK }
24
+ EXP_MATH = ->(val, op, arg) { val.to_i.send(op, arg.to_i) }
25
+ EXP_APPEND = ->(val, _op, arg) { val + arg }
26
+ EXP_PREPEND = ->(val, _op, arg) { arg + val }
17
27
 
18
28
  def self.parse!(tokens)
19
29
  nodes = []
@@ -45,7 +55,7 @@ module Fop
45
55
  when Nodes::Text, Nodes::Op
46
56
  nodes << curr_node
47
57
  else
48
- raise "Unexpected end node #{curr_node}"
58
+ raise Error, "Unexpected end node #{curr_node}"
49
59
  end
50
60
 
51
61
  nodes
@@ -59,7 +69,7 @@ module Fop
59
69
  Nodes::Text.new(wildcard, token.char.clone)
60
70
  when Tokenizer::Op
61
71
  op = Nodes::Op.new(wildcard)
62
- parse_op! op, token.tokens
72
+ parse_op! op, token
63
73
  op
64
74
  when :wildcard
65
75
  :wildcard
@@ -85,52 +95,87 @@ module Fop
85
95
  end
86
96
  end
87
97
 
88
- def self.parse_op!(node, tokens)
89
- t = tokens[0] || raise(Error, "Empty operation")
98
+ def self.parse_op!(node, token)
90
99
  # parse the matching type
91
100
  node.regex =
92
- case t
101
+ case token.match
93
102
  when Tokenizer::Char
94
- node.match = t.char
103
+ node.match = token.match.char
95
104
  node.regex_match = false
96
- case t.char
105
+ case node.match
97
106
  when MATCH_NUM then Regexp.new((node.wildcard ? ".*?" : "^") + "[0-9]+")
98
107
  when MATCH_WORD then Regexp.new((node.wildcard ? ".*?" : "^") + "\\w+")
99
108
  when MATCH_ALPHA then Regexp.new((node.wildcard ? ".*?" : "^") + "[a-zA-Z]+")
100
109
  when MATCH_WILD then /.*/
101
- else raise Error, "Unknown match type '#{t.char}'"
110
+ else raise Error, "Unknown match type '#{node.match}'"
102
111
  end
103
112
  when Tokenizer::Regex
104
- node.match = "/#{t.src}/"
113
+ node.match = "/#{token.match.src}/"
105
114
  node.regex_match = true
106
- Regexp.new((node.wildcard ? ".*?" : "^") + t.src)
115
+ Regexp.new((node.wildcard ? ".*?" : "^") + token.match.src)
116
+ when nil
117
+ raise Error, "Empty operation"
107
118
  else
108
- raise Error, "Unexpected token #{t}"
119
+ raise Error, "Unexpected #{token.match}"
109
120
  end
110
121
 
111
122
  # parse the operator (if any)
112
- if (op = tokens[1])
113
- raise Error, "Unexpected #{op}" unless op.is_a? Tokenizer::Char
114
- node.operator = op.char
115
-
116
- arg = tokens[2..-1].reduce("") { |acc, t|
117
- raise Error, "Unexpected #{t}" unless t.is_a? Tokenizer::Char
118
- acc + t.char
119
- }
120
- node.operator_arg = arg == BLANK ? nil : arg
121
-
123
+ if token.operator
124
+ raise Error, "Unexpected #{token.operator} for operator" unless token.operator.is_a? Tokenizer::Char
125
+ node.operator = token.operator.char
126
+ node.operator_arg = token.arg if token.arg and token.arg != BLANK
127
+ node.operator_arg_w_caps = parse_captures! node.operator_arg if node.operator_arg and node.regex_match
122
128
  node.expression =
123
129
  case node.operator
124
130
  when OP_REPLACE
125
- ->(_) { node.operator_arg || BLANK }
131
+ EXP_REPLACE
126
132
  when OP_ADD, OP_SUB, OP_MUL, OP_DIV
127
133
  raise Error, "Operator #{node.operator} is only available for numeric matches" unless node.match == MATCH_NUM
128
134
  raise Error, "Operator #{node.operator} expects an argument" if node.operator_arg.nil?
129
- ->(x) { x.to_i.send(node.operator, node.operator_arg.to_i) }
135
+ EXP_MATH
136
+ when OP_APPEND
137
+ raise Error, "Operator #{node.operator} expects an argument" if node.operator_arg.nil?
138
+ EXP_APPEND
139
+ when OP_PREPEND
140
+ raise Error, "Operator #{node.operator} expects an argument" if node.operator_arg.nil?
141
+ EXP_PREPEND
130
142
  else
131
- raise(Error, "Unknown operator #{node.operator}")
143
+ raise Error, "Unknown operator #{node.operator}"
132
144
  end
133
145
  end
134
146
  end
147
+
148
+ def self.parse_captures!(arg)
149
+ i = 0
150
+ iend = arg.size - 1
151
+ escape = false
152
+ nodes = []
153
+
154
+ until i > iend
155
+ char = arg[i]
156
+ i += 1
157
+
158
+ if escape
159
+ nodes << char
160
+ escape = false
161
+ next
162
+ end
163
+
164
+ case char
165
+ when Tokenizer::ESCAPE
166
+ escape = true
167
+ when VAR
168
+ num = arg[i].to_s
169
+ raise Error, "Capture group number must be between 1 and 9; found '#{num}'" unless num =~ CAP_NUM
170
+ nodes << CaptureGroup.new(num.to_i - 1)
171
+ i += 1
172
+ else
173
+ nodes << char
174
+ end
175
+ end
176
+
177
+ raise Error, "Trailing escape" if escape
178
+ nodes
179
+ end
135
180
  end
136
181
  end
data/lib/fop/tokenizer.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module Fop
2
2
  class Tokenizer
3
3
  Char = Struct.new(:char)
4
- Op = Struct.new(:tokens)
4
+ Op = Struct.new(:match, :operator, :arg)
5
5
  Regex = Struct.new(:src)
6
6
  Error = Class.new(StandardError)
7
7
 
@@ -22,28 +22,26 @@ module Fop
22
22
  i = 0
23
23
  until i > @end do
24
24
  char = @src[i]
25
+ i += 1
26
+
25
27
  if escape
26
28
  tokens << Char.new(char)
27
29
  escape = false
28
- i += 1
29
30
  next
30
31
  end
31
32
 
32
33
  case char
33
34
  when ESCAPE
34
35
  escape = true
35
- i += 1
36
36
  when OP_OPEN
37
- i, op = operation! i + 1
37
+ i, op = operation! i
38
38
  tokens << op
39
39
  when OP_CLOSE
40
40
  raise "Unexpected #{OP_CLOSE}"
41
41
  when WILDCARD
42
42
  tokens << :wildcard
43
- i += 1
44
43
  else
45
44
  tokens << Char.new(char)
46
- i += 1
47
45
  end
48
46
  end
49
47
 
@@ -54,40 +52,63 @@ module Fop
54
52
  private
55
53
 
56
54
  def operation!(i)
57
- escape = false
58
55
  found_close = false
59
- tokens = []
56
+ op = Op.new(nil, nil, "")
60
57
 
58
+ # Find matcher
59
+ until found_close or op.match or i > @end do
60
+ char = @src[i]
61
+ i += 1
62
+ case char
63
+ when OP_CLOSE
64
+ found_close = true
65
+ when REGEX_MARKER
66
+ i, reg = regex! i
67
+ op.match = reg
68
+ else
69
+ op.match = Char.new(char)
70
+ end
71
+ end
72
+
73
+ # Find operator
74
+ until found_close or op.operator or i > @end do
75
+ char = @src[i]
76
+ i += 1
77
+ case char
78
+ when OP_CLOSE
79
+ found_close = true
80
+ else
81
+ op.operator = Char.new(char)
82
+ end
83
+ end
84
+
85
+ # Find operator arg
86
+ escape = false
61
87
  until found_close or i > @end do
62
88
  char = @src[i]
89
+ i += 1
90
+
63
91
  if escape
64
- tokens << Char.new(char)
92
+ op.arg << char
65
93
  escape = false
66
- i += 1
67
94
  next
68
95
  end
69
96
 
70
97
  case char
71
98
  when ESCAPE
72
99
  escape = true
73
- i += 1
74
100
  when OP_OPEN
75
101
  raise "Unexpected #{OP_OPEN}"
76
102
  when OP_CLOSE
77
103
  found_close = true
78
- i += 1
79
- when REGEX_MARKER
80
- i, reg = regex! i + 1
81
- tokens << reg
82
104
  else
83
- tokens << Char.new(char)
84
- i += 1
105
+ op.arg << char
85
106
  end
86
107
  end
87
108
 
88
109
  raise Error, "Unclosed operation" if !found_close
89
110
  raise Error, "Trailing escape" if escape
90
- return i, Op.new(tokens)
111
+ return i, op
91
112
  end
92
113
 
93
114
  def regex!(i)
data/lib/fop/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Fop
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fop_lang
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jordan Hollinger