parsanol 3.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.

Potentially problematic release.


This version of parsanol might be problematic. Click here for more details.

Files changed (336) hide show
  1. checksums.yaml +7 -0
  2. data/HISTORY.txt +25 -0
  3. data/LICENSE +23 -0
  4. data/README.adoc +643 -0
  5. data/Rakefile +189 -0
  6. data/example/balanced-parens/basic.rb +42 -0
  7. data/example/balanced-parens/basic.rb.md +86 -0
  8. data/example/balanced-parens/parens.rb +42 -0
  9. data/example/balanced-parens/ruby_transform.rb +162 -0
  10. data/example/big.erb +73 -0
  11. data/example/boolean-algebra/basic.rb +70 -0
  12. data/example/boolean-algebra/basic.rb.md +108 -0
  13. data/example/boolean-algebra/ruby_transform.rb +263 -0
  14. data/example/calculator/basic.rb +153 -0
  15. data/example/calculator/basic.rb.md +120 -0
  16. data/example/calculator/pattern.rb +153 -0
  17. data/example/calculator/ruby_transform.rb +156 -0
  18. data/example/calculator/ruby_transform.rb.md +32 -0
  19. data/example/calculator/serialized.rb +257 -0
  20. data/example/calculator/serialized.rb.md +32 -0
  21. data/example/calculator/transform.rb +153 -0
  22. data/example/calculator/zero_copy.rb +269 -0
  23. data/example/calculator/zero_copy.rb.md +36 -0
  24. data/example/capture/basic.rb +49 -0
  25. data/example/capture/basic.rb.md +106 -0
  26. data/example/capture/example.json +39 -0
  27. data/example/comments/basic.rb +35 -0
  28. data/example/comments/basic.rb.md +110 -0
  29. data/example/csv/ruby_transform.rb +148 -0
  30. data/example/csv/ruby_transform.rb.md +131 -0
  31. data/example/csv/serialized.rb +201 -0
  32. data/example/csv/serialized.rb.md +31 -0
  33. data/example/csv/zero_copy.rb +276 -0
  34. data/example/csv/zero_copy.rb.md +36 -0
  35. data/example/custom_atoms/indent_atom.rb +79 -0
  36. data/example/deepest-errors/basic.rb +131 -0
  37. data/example/deepest-errors/basic.rb.md +152 -0
  38. data/example/documentation/basic.rb +18 -0
  39. data/example/documentation/basic.rb.md +97 -0
  40. data/example/email/basic.rb +55 -0
  41. data/example/email/basic.rb.md +102 -0
  42. data/example/email/ruby_transform.rb +106 -0
  43. data/example/empty/basic.rb +13 -0
  44. data/example/empty/basic.rb.md +73 -0
  45. data/example/empty/example.json +38 -0
  46. data/example/erb/basic.rb +47 -0
  47. data/example/erb/basic.rb.md +103 -0
  48. data/example/erb/optimized.rb +42 -0
  49. data/example/error-reporting/basic.rb +132 -0
  50. data/example/error-reporting/basic.rb.md +122 -0
  51. data/example/expression-evaluator/basic.rb +284 -0
  52. data/example/expression-evaluator/basic.rb.md +138 -0
  53. data/example/ini/basic.rb +154 -0
  54. data/example/ini/basic.rb.md +129 -0
  55. data/example/ini/ruby_transform.rb +154 -0
  56. data/example/ip-address/basic.rb +125 -0
  57. data/example/ip-address/basic.rb.md +139 -0
  58. data/example/iso-6709/basic.rb +231 -0
  59. data/example/iso-6709/basic.rb.md +143 -0
  60. data/example/iso-8601/basic.rb +275 -0
  61. data/example/iso-8601/basic.rb.md +149 -0
  62. data/example/json/basic.rb +128 -0
  63. data/example/json/basic.rb.md +121 -0
  64. data/example/json/pattern.rb +128 -0
  65. data/example/json/ruby_transform.rb +200 -0
  66. data/example/json/ruby_transform.rb.md +32 -0
  67. data/example/json/serialized.rb +233 -0
  68. data/example/json/serialized.rb.md +31 -0
  69. data/example/json/transform.rb +128 -0
  70. data/example/json/zero_copy.rb +316 -0
  71. data/example/json/zero_copy.rb.md +36 -0
  72. data/example/local/basic.rb +34 -0
  73. data/example/local/basic.rb.md +91 -0
  74. data/example/local/example.json +38 -0
  75. data/example/markdown/basic.rb +287 -0
  76. data/example/markdown/basic.rb.md +160 -0
  77. data/example/markup/basic.rb +173 -0
  78. data/example/markup/basic.rb.md +118 -0
  79. data/example/mathn/basic.rb +47 -0
  80. data/example/mathn/basic.rb.md +96 -0
  81. data/example/mathn/example.json +39 -0
  82. data/example/minilisp/basic.rb +94 -0
  83. data/example/minilisp/basic.rb.md +133 -0
  84. data/example/modularity/basic.rb +47 -0
  85. data/example/modularity/basic.rb.md +152 -0
  86. data/example/nested-errors/basic.rb +132 -0
  87. data/example/nested-errors/basic.rb.md +157 -0
  88. data/example/output/boolean_algebra.out +4 -0
  89. data/example/output/calc.out +1 -0
  90. data/example/output/capture.out +3 -0
  91. data/example/output/comments.out +8 -0
  92. data/example/output/deepest_errors.out +54 -0
  93. data/example/output/documentation.err +4 -0
  94. data/example/output/documentation.out +1 -0
  95. data/example/output/email_parser.out +2 -0
  96. data/example/output/empty.err +1 -0
  97. data/example/output/erb.out +7 -0
  98. data/example/output/ignore.out +1 -0
  99. data/example/output/ignore_whitespace.out +1 -0
  100. data/example/output/ip_address.out +9 -0
  101. data/example/output/json.out +5 -0
  102. data/example/output/local.out +3 -0
  103. data/example/output/mathn.out +4 -0
  104. data/example/output/minilisp.out +5 -0
  105. data/example/output/modularity.out +0 -0
  106. data/example/output/nested_errors.out +54 -0
  107. data/example/output/optimized_erb.out +1 -0
  108. data/example/output/parens.out +8 -0
  109. data/example/output/prec_calc.out +5 -0
  110. data/example/output/readme.out +1 -0
  111. data/example/output/scopes.out +1 -0
  112. data/example/output/seasons.out +28 -0
  113. data/example/output/sentence.out +1 -0
  114. data/example/output/simple_xml.out +2 -0
  115. data/example/output/string_parser.out +3 -0
  116. data/example/prec-calc/basic.rb +71 -0
  117. data/example/prec-calc/basic.rb.md +114 -0
  118. data/example/readme/basic.rb +30 -0
  119. data/example/readme/basic.rb.md +80 -0
  120. data/example/scopes/basic.rb +15 -0
  121. data/example/scopes/basic.rb.md +73 -0
  122. data/example/scopes/example.json +38 -0
  123. data/example/seasons/basic.rb +46 -0
  124. data/example/seasons/basic.rb.md +117 -0
  125. data/example/seasons/example.json +40 -0
  126. data/example/sentence/basic.rb +36 -0
  127. data/example/sentence/basic.rb.md +81 -0
  128. data/example/sexp/ruby_transform.rb +180 -0
  129. data/example/sexp/ruby_transform.rb.md +143 -0
  130. data/example/simple-xml/basic.rb +54 -0
  131. data/example/simple-xml/basic.rb.md +125 -0
  132. data/example/simple.lit +3 -0
  133. data/example/string-literal/basic.rb +77 -0
  134. data/example/string-literal/basic.rb.md +128 -0
  135. data/example/test.lit +4 -0
  136. data/example/toml/basic.rb +226 -0
  137. data/example/toml/basic.rb.md +173 -0
  138. data/example/url/basic.rb +219 -0
  139. data/example/url/basic.rb.md +142 -0
  140. data/example/url/ruby_transform.rb +219 -0
  141. data/example/yaml/basic.rb +216 -0
  142. data/example/yaml/basic.rb.md +148 -0
  143. data/ext/parsanol_native/extconf.rb +4 -0
  144. data/lib/parsanol/accelerator/application.rb +62 -0
  145. data/lib/parsanol/accelerator/engine.rb +112 -0
  146. data/lib/parsanol/accelerator.rb +162 -0
  147. data/lib/parsanol/ast_visitor.rb +122 -0
  148. data/lib/parsanol/atoms/alternative.rb +97 -0
  149. data/lib/parsanol/atoms/base.rb +214 -0
  150. data/lib/parsanol/atoms/can_flatten.rb +192 -0
  151. data/lib/parsanol/atoms/capture.rb +41 -0
  152. data/lib/parsanol/atoms/context.rb +351 -0
  153. data/lib/parsanol/atoms/context_optimized.rb +42 -0
  154. data/lib/parsanol/atoms/custom.rb +110 -0
  155. data/lib/parsanol/atoms/cut.rb +62 -0
  156. data/lib/parsanol/atoms/dsl.rb +130 -0
  157. data/lib/parsanol/atoms/dynamic.rb +33 -0
  158. data/lib/parsanol/atoms/entity.rb +55 -0
  159. data/lib/parsanol/atoms/ignored.rb +28 -0
  160. data/lib/parsanol/atoms/infix.rb +121 -0
  161. data/lib/parsanol/atoms/lookahead.rb +64 -0
  162. data/lib/parsanol/atoms/named.rb +50 -0
  163. data/lib/parsanol/atoms/re.rb +61 -0
  164. data/lib/parsanol/atoms/repetition.rb +241 -0
  165. data/lib/parsanol/atoms/scope.rb +28 -0
  166. data/lib/parsanol/atoms/sequence.rb +157 -0
  167. data/lib/parsanol/atoms/str.rb +90 -0
  168. data/lib/parsanol/atoms/visitor.rb +91 -0
  169. data/lib/parsanol/atoms.rb +36 -0
  170. data/lib/parsanol/buffer.rb +130 -0
  171. data/lib/parsanol/builder_callbacks.rb +353 -0
  172. data/lib/parsanol/cause.rb +101 -0
  173. data/lib/parsanol/context.rb +23 -0
  174. data/lib/parsanol/convenience.rb +35 -0
  175. data/lib/parsanol/edit_tracker.rb +107 -0
  176. data/lib/parsanol/error_reporter/contextual.rb +122 -0
  177. data/lib/parsanol/error_reporter/deepest.rb +106 -0
  178. data/lib/parsanol/error_reporter/tree.rb +68 -0
  179. data/lib/parsanol/error_reporter.rb +98 -0
  180. data/lib/parsanol/export.rb +163 -0
  181. data/lib/parsanol/expression/treetop.rb +94 -0
  182. data/lib/parsanol/expression.rb +51 -0
  183. data/lib/parsanol/fast_mode.rb +145 -0
  184. data/lib/parsanol/first_set.rb +75 -0
  185. data/lib/parsanol/grammar_builder.rb +177 -0
  186. data/lib/parsanol/graphviz.rb +97 -0
  187. data/lib/parsanol/incremental_parser.rb +179 -0
  188. data/lib/parsanol/interval_tree.rb +215 -0
  189. data/lib/parsanol/lazy_result.rb +178 -0
  190. data/lib/parsanol/lexer.rb +146 -0
  191. data/lib/parsanol/native/parser.rb +630 -0
  192. data/lib/parsanol/native/serializer.rb +245 -0
  193. data/lib/parsanol/native/transformer.rb +438 -0
  194. data/lib/parsanol/native/types.rb +41 -0
  195. data/lib/parsanol/native.rb +217 -0
  196. data/lib/parsanol/optimizer.rb +86 -0
  197. data/lib/parsanol/optimizers/choice_optimizer.rb +78 -0
  198. data/lib/parsanol/optimizers/cut_inserter.rb +175 -0
  199. data/lib/parsanol/optimizers/lookahead_optimizer.rb +58 -0
  200. data/lib/parsanol/optimizers/quantifier_optimizer.rb +62 -0
  201. data/lib/parsanol/optimizers/sequence_optimizer.rb +97 -0
  202. data/lib/parsanol/options/ruby_transform.rb +109 -0
  203. data/lib/parsanol/options/serialized.rb +94 -0
  204. data/lib/parsanol/options/zero_copy.rb +130 -0
  205. data/lib/parsanol/options.rb +20 -0
  206. data/lib/parsanol/parallel.rb +133 -0
  207. data/lib/parsanol/parsanol_native.bundle +0 -0
  208. data/lib/parsanol/parser.rb +151 -0
  209. data/lib/parsanol/parslet.rb +148 -0
  210. data/lib/parsanol/parslet_native.bundle +0 -0
  211. data/lib/parsanol/pattern/binding.rb +49 -0
  212. data/lib/parsanol/pattern.rb +115 -0
  213. data/lib/parsanol/pool.rb +220 -0
  214. data/lib/parsanol/pools/array_pool.rb +75 -0
  215. data/lib/parsanol/pools/buffer_pool.rb +173 -0
  216. data/lib/parsanol/pools/position_pool.rb +92 -0
  217. data/lib/parsanol/pools/slice_pool.rb +64 -0
  218. data/lib/parsanol/position.rb +89 -0
  219. data/lib/parsanol/result.rb +44 -0
  220. data/lib/parsanol/result_builder.rb +208 -0
  221. data/lib/parsanol/result_stream.rb +262 -0
  222. data/lib/parsanol/rig/rspec.rb +52 -0
  223. data/lib/parsanol/rope.rb +78 -0
  224. data/lib/parsanol/scope.rb +42 -0
  225. data/lib/parsanol/slice.rb +172 -0
  226. data/lib/parsanol/source/line_cache.rb +99 -0
  227. data/lib/parsanol/source.rb +171 -0
  228. data/lib/parsanol/source_location.rb +164 -0
  229. data/lib/parsanol/streaming_parser.rb +124 -0
  230. data/lib/parsanol/string_view.rb +192 -0
  231. data/lib/parsanol/transform.rb +267 -0
  232. data/lib/parsanol/version.rb +5 -0
  233. data/lib/parsanol/wasm/README.md +80 -0
  234. data/lib/parsanol/wasm/package.json +51 -0
  235. data/lib/parsanol/wasm/parsanol.js +252 -0
  236. data/lib/parsanol/wasm/parslet.d.ts +129 -0
  237. data/lib/parsanol/wasm_parser.rb +239 -0
  238. data/lib/parsanol.rb +408 -0
  239. data/parsanol-ruby.gemspec +56 -0
  240. data/spec/acceptance/examples_spec.rb +96 -0
  241. data/spec/acceptance/infix_parser_spec.rb +145 -0
  242. data/spec/acceptance/mixing_parsers_spec.rb +74 -0
  243. data/spec/acceptance/regression_spec.rb +329 -0
  244. data/spec/acceptance/repetition_and_maybe_spec.rb +44 -0
  245. data/spec/acceptance/unconsumed_input_spec.rb +21 -0
  246. data/spec/benchmark/comparative/runner_spec.rb +105 -0
  247. data/spec/integration/array_pooling_spec.rb +193 -0
  248. data/spec/integration/buffer_allocation_spec.rb +324 -0
  249. data/spec/integration/position_pooling_spec.rb +184 -0
  250. data/spec/integration/result_builder_spec.rb +282 -0
  251. data/spec/integration/rope_stringview_integration_spec.rb +188 -0
  252. data/spec/integration/slice_pooling_spec.rb +63 -0
  253. data/spec/integration/string_view_integration_spec.rb +125 -0
  254. data/spec/lexer_spec.rb +231 -0
  255. data/spec/parsanol/atom_results_spec.rb +39 -0
  256. data/spec/parsanol/atoms/alternative_spec.rb +26 -0
  257. data/spec/parsanol/atoms/base_spec.rb +127 -0
  258. data/spec/parsanol/atoms/capture_spec.rb +21 -0
  259. data/spec/parsanol/atoms/combinations_spec.rb +5 -0
  260. data/spec/parsanol/atoms/custom_spec.rb +79 -0
  261. data/spec/parsanol/atoms/dsl_spec.rb +7 -0
  262. data/spec/parsanol/atoms/entity_spec.rb +77 -0
  263. data/spec/parsanol/atoms/ignored_spec.rb +15 -0
  264. data/spec/parsanol/atoms/infix_spec.rb +5 -0
  265. data/spec/parsanol/atoms/lookahead_spec.rb +22 -0
  266. data/spec/parsanol/atoms/named_spec.rb +4 -0
  267. data/spec/parsanol/atoms/re_spec.rb +14 -0
  268. data/spec/parsanol/atoms/repetition_spec.rb +24 -0
  269. data/spec/parsanol/atoms/scope_spec.rb +26 -0
  270. data/spec/parsanol/atoms/sequence_spec.rb +28 -0
  271. data/spec/parsanol/atoms/str_spec.rb +15 -0
  272. data/spec/parsanol/atoms/visitor_spec.rb +101 -0
  273. data/spec/parsanol/atoms_spec.rb +488 -0
  274. data/spec/parsanol/auto_optimize_spec.rb +334 -0
  275. data/spec/parsanol/buffer_spec.rb +219 -0
  276. data/spec/parsanol/builder_callbacks_spec.rb +377 -0
  277. data/spec/parsanol/choice_optimizer_spec.rb +231 -0
  278. data/spec/parsanol/convenience_spec.rb +54 -0
  279. data/spec/parsanol/cut_inserter_spec.rb +248 -0
  280. data/spec/parsanol/cut_spec.rb +66 -0
  281. data/spec/parsanol/edit_tracker_spec.rb +218 -0
  282. data/spec/parsanol/error_reporter/contextual_spec.rb +122 -0
  283. data/spec/parsanol/error_reporter/deepest_spec.rb +82 -0
  284. data/spec/parsanol/error_reporter/tree_spec.rb +7 -0
  285. data/spec/parsanol/export_spec.rb +67 -0
  286. data/spec/parsanol/expression/treetop_spec.rb +75 -0
  287. data/spec/parsanol/first_set_spec.rb +298 -0
  288. data/spec/parsanol/interval_tree_spec.rb +205 -0
  289. data/spec/parsanol/lazy_result_spec.rb +288 -0
  290. data/spec/parsanol/lookahead_optimizer_spec.rb +252 -0
  291. data/spec/parsanol/minilisp.citrus +29 -0
  292. data/spec/parsanol/minilisp.tt +29 -0
  293. data/spec/parsanol/optimizer_spec.rb +459 -0
  294. data/spec/parsanol/options/parslet_compat_spec.rb +166 -0
  295. data/spec/parsanol/options/ruby_transform_spec.rb +70 -0
  296. data/spec/parsanol/options/serialized_spec.rb +69 -0
  297. data/spec/parsanol/options/zero_copy_spec.rb +230 -0
  298. data/spec/parsanol/parser_spec.rb +36 -0
  299. data/spec/parsanol/parslet_spec.rb +38 -0
  300. data/spec/parsanol/pattern_spec.rb +272 -0
  301. data/spec/parsanol/pool_spec.rb +392 -0
  302. data/spec/parsanol/pools/array_pool_spec.rb +356 -0
  303. data/spec/parsanol/pools/buffer_pool_spec.rb +365 -0
  304. data/spec/parsanol/pools/position_pool_spec.rb +118 -0
  305. data/spec/parsanol/pools/slice_pool_spec.rb +262 -0
  306. data/spec/parsanol/position_spec.rb +14 -0
  307. data/spec/parsanol/result_builder_spec.rb +391 -0
  308. data/spec/parsanol/rig/rspec_spec.rb +54 -0
  309. data/spec/parsanol/rope_spec.rb +207 -0
  310. data/spec/parsanol/scope_spec.rb +45 -0
  311. data/spec/parsanol/slice_spec.rb +249 -0
  312. data/spec/parsanol/source/line_cache_spec.rb +74 -0
  313. data/spec/parsanol/source_spec.rb +207 -0
  314. data/spec/parsanol/string_view_spec.rb +345 -0
  315. data/spec/parsanol/transform/context_spec.rb +56 -0
  316. data/spec/parsanol/transform_spec.rb +183 -0
  317. data/spec/parsanol/tree_memoization_spec.rb +149 -0
  318. data/spec/parslet_compatibility/expressir_edge_cases_spec.rb +153 -0
  319. data/spec/parslet_compatibility/minimal_reproduction.rb +199 -0
  320. data/spec/parslet_compatibility_spec.rb +399 -0
  321. data/spec/parslet_imported/atom_spec.rb +93 -0
  322. data/spec/parslet_imported/combinator_spec.rb +161 -0
  323. data/spec/parslet_imported/spec_helper.rb +73 -0
  324. data/spec/performance/batch_parsing_benchmark.rb +129 -0
  325. data/spec/performance/complete_optimization_summary.rb +143 -0
  326. data/spec/performance/grammar_caching_analysis.rb +121 -0
  327. data/spec/performance/grammar_caching_benchmark.rb +80 -0
  328. data/spec/performance/native_benchmark_spec.rb +230 -0
  329. data/spec/performance/phase5_benchmark.rb +144 -0
  330. data/spec/performance/profiling_benchmark.rb +131 -0
  331. data/spec/performance/ruby_improvements_benchmark.rb +171 -0
  332. data/spec/performance_spec.rb +374 -0
  333. data/spec/spec_helper.rb +79 -0
  334. data/spec/support/opal.rb +8 -0
  335. data/spec/support/opal.rb.erb +14 -0
  336. metadata +485 -0
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parsanol::Atoms::Repetition do
4
+ include Parsanol
5
+
6
+ describe "repeat" do
7
+ let(:parslet) { str('a') }
8
+
9
+ describe "(min, max)" do
10
+ subject { parslet.repeat(1,2) }
11
+
12
+ it { should_not parse("") }
13
+ it { should parse("a") }
14
+ it { should parse("aa") }
15
+ end
16
+ describe "0 times" do
17
+ it "raises an ArgumentError" do
18
+ expect {
19
+ parslet.repeat(0,0)
20
+ }.to raise_error(ArgumentError)
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parsanol::Atoms::Scope do
4
+ include Parsanol
5
+ include Parsanol::Atoms::DSL
6
+
7
+
8
+ let(:context) { Parsanol::Atoms::Context.new(nil) }
9
+ let(:captures) { context.captures }
10
+
11
+ def inject string, parser
12
+ source = Parsanol::Source.new(string)
13
+ parser.apply(source, context, true)
14
+ end
15
+
16
+ let(:aabb) {
17
+ scope {
18
+ match['ab'].capture(:f) >> dynamic { |s,c| str(c.captures[:f]) }
19
+ }
20
+ }
21
+ it "keeps values of captures outside" do
22
+ captures[:f] = 'old_value'
23
+ inject 'aa', aabb
24
+ captures[:f].should == 'old_value'
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parsanol::Atoms::Sequence do
4
+ include Parsanol
5
+
6
+ let(:sequence) { described_class.new }
7
+
8
+ describe '>> shortcut' do
9
+ let(:sequence) { str('a') >> str('b') }
10
+
11
+ context "when chained with different atoms" do
12
+ before(:each) {
13
+ # Chain something else to the sequence parslet. If it modifies the
14
+ # parslet atom in place, we'll notice:
15
+
16
+ sequence >> str('d')
17
+ }
18
+ let!(:chained) { sequence >> str('c') }
19
+
20
+
21
+ it "is side-effect free" do
22
+ chained.should parse('abc')
23
+ chained.should_not parse('abdc')
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,15 @@
1
+ # Encoding: UTF-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Parsanol::Atoms::Str do
6
+ def str(s)
7
+ described_class.new(s)
8
+ end
9
+
10
+ describe 'regression #1: multibyte characters' do
11
+ it "parses successfully (length check works)" do
12
+ str('あああ').should parse('あああ')
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,101 @@
1
+ require 'spec_helper'
2
+
3
+ describe Parsanol::Atoms do
4
+ include Parsanol
5
+ let(:visitor) { double(:visitor) }
6
+
7
+ describe Parsanol::Atoms::Str do
8
+ let(:parslet) { str('foo') }
9
+
10
+ it 'calls back visitor' do
11
+ expect(visitor).to receive(:visit_str).with('foo').once
12
+
13
+ parslet.accept(visitor)
14
+ end
15
+ end
16
+
17
+ describe Parsanol::Atoms::Re do
18
+ let(:parslet) { match['abc'] }
19
+
20
+ it 'calls back visitor' do
21
+ expect(visitor).to receive(:visit_re).with('[abc]').once
22
+
23
+ parslet.accept(visitor)
24
+ end
25
+ end
26
+
27
+ describe Parsanol::Atoms::Sequence do
28
+ let(:parslet) { str('a') >> str('b') }
29
+
30
+ it 'calls back visitor' do
31
+ expect(visitor).to receive(:visit_sequence).with(Array).once
32
+
33
+ parslet.accept(visitor)
34
+ end
35
+ end
36
+
37
+ describe Parsanol::Atoms::Repetition do
38
+ let(:parslet) { str('a').repeat(1, 2) }
39
+
40
+ it 'calls back visitor' do
41
+ expect(visitor).to receive(:visit_repetition).with(:repetition, 1, 2, Parsanol::Atoms::Base).once
42
+
43
+ parslet.accept(visitor)
44
+ end
45
+ end
46
+
47
+ describe Parsanol::Atoms::Alternative do
48
+ let(:parslet) { str('a') | str('b') }
49
+
50
+ it 'calls back visitor' do
51
+ expect(visitor).to receive(:visit_alternative).with(Array).once
52
+
53
+ parslet.accept(visitor)
54
+ end
55
+ end
56
+
57
+ describe Parsanol::Atoms::Named do
58
+ let(:parslet) { str('a').as(:a) }
59
+
60
+ it 'calls back visitor' do
61
+ expect(visitor).to receive(:visit_named).with(:a, Parsanol::Atoms::Base).once
62
+
63
+ parslet.accept(visitor)
64
+ end
65
+ end
66
+
67
+ describe Parsanol::Atoms::Entity do
68
+ let(:parslet) { Parsanol::Atoms::Entity.new('foo', &-> {}) }
69
+
70
+ it 'calls back visitor' do
71
+ expect(visitor).to receive(:visit_entity).with('foo', Proc).once
72
+
73
+ parslet.accept(visitor)
74
+ end
75
+ end
76
+
77
+ describe Parsanol::Atoms::Lookahead do
78
+ let(:parslet) { str('a').absent? }
79
+
80
+ it 'calls back visitor' do
81
+ expect(visitor).to receive(:visit_lookahead).with(false, Parsanol::Atoms::Base).once
82
+
83
+ parslet.accept(visitor)
84
+ end
85
+ end
86
+
87
+ describe '< Parsanol::Parser' do
88
+ let(:parslet) do
89
+ Class.new(Parsanol::Parser) do
90
+ rule(:test_rule) { str('test') }
91
+ root(:test_rule)
92
+ end.new
93
+ end
94
+
95
+ it 'calls back to visitor' do
96
+ expect(visitor).to receive(:visit_parser).with(Parsanol::Atoms::Base).once
97
+
98
+ parslet.accept(visitor)
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,488 @@
1
+ require 'spec_helper'
2
+
3
+ require 'timeout' unless RUBY_ENGINE == 'opal'
4
+ require 'parsanol/parslet'
5
+
6
+ describe Parsanol do
7
+ def not_parse
8
+ raise_error(Parsanol::ParseFailed)
9
+ end
10
+
11
+ include Parsanol
12
+ extend Parsanol
13
+
14
+ def src(str)
15
+ Parsanol::Source.new str
16
+ end
17
+ let(:context) { Parsanol::Atoms::Context.new }
18
+
19
+ describe "match('[abc]')" do
20
+ attr_reader :parslet
21
+
22
+ before do
23
+ @parslet = match('[abc]')
24
+ end
25
+
26
+ it 'parses {a,b,c}' do
27
+ parslet.parse('a')
28
+ parslet.parse('b')
29
+ parslet.parse('c')
30
+ end
31
+
32
+ it 'does not parse d' do
33
+ cause = catch_failed_parse do
34
+ parslet.parse('d')
35
+ end
36
+ expect(cause.to_s).to eq('Failed to match [abc] at line 1 char 1.')
37
+ end
38
+
39
+ it 'prints as [abc]' do
40
+ parslet.inspect.should == '[abc]'
41
+ end
42
+ end
43
+
44
+ describe "match(['[a]').repeat(3)" do
45
+ attr_reader :parslet
46
+
47
+ before do
48
+ @parslet = match('[a]').repeat(3)
49
+ end
50
+
51
+ context "when failing on input 'aa'" do
52
+ let!(:cause) do
53
+ catch_failed_parse { parslet.parse('aa') }
54
+ end
55
+
56
+ it 'has a relevant cause' do
57
+ cause.to_s.should == 'Expected at least 3 of [a] at line 1 char 1.'
58
+ end
59
+
60
+ it 'has a tree with 2 nodes' do
61
+ cause.children.size.should == 1
62
+ end
63
+ end
64
+
65
+ it "succeeds on 'aaa'" do
66
+ parslet.parse('aaa')
67
+ end
68
+
69
+ it "succeeds on many 'a'" do
70
+ parslet.parse('a' * 100)
71
+ end
72
+
73
+ it 'inspects as [a]{3, }' do
74
+ parslet.inspect.should == '[a]{3, }'
75
+ end
76
+ end
77
+
78
+ describe "str('foo')" do
79
+ attr_reader :parslet
80
+
81
+ before do
82
+ @parslet = str('foo')
83
+ end
84
+
85
+ it "parses 'foo'" do
86
+ parslet.parse('foo')
87
+ end
88
+
89
+ it "does not parse 'bar'" do
90
+ cause = catch_failed_parse { parslet.parse('bar') }
91
+ cause.to_s.should ==
92
+ 'Expected "foo", but got "bar" at line 1 char 1.'
93
+ end
94
+
95
+ it "inspects as 'foo'" do
96
+ parslet.inspect.should == "'foo'"
97
+ end
98
+ end
99
+
100
+ describe "str('foo').maybe" do
101
+ let(:parslet) { str('foo').maybe }
102
+
103
+ it 'parses a foo' do
104
+ parslet.parse('foo')
105
+ end
106
+
107
+ it 'leaves pos untouched if there is no foo' do
108
+ source = src('bar')
109
+ parslet.apply(source, context)
110
+ source.pos.should == 0
111
+ end
112
+
113
+ it "inspects as 'foo'?" do
114
+ parslet.inspect.should == "'foo'?"
115
+ end
116
+
117
+ context "when parsing 'foo'" do
118
+ subject { parslet.parse('foo') }
119
+
120
+ it { is_expected.to eq('foo') }
121
+ end
122
+
123
+ context "when parsing ''" do
124
+ subject { parslet.parse('') }
125
+
126
+ it { is_expected.to eq('') }
127
+ end
128
+ end
129
+
130
+ describe "str('foo') >> str('bar')" do
131
+ let(:parslet) { str('foo') >> str('bar') }
132
+
133
+ context "when it fails on input 'foobaz'" do
134
+ let!(:cause) do
135
+ catch_failed_parse { parslet.parse('foobaz') }
136
+ end
137
+
138
+ it "does not parse 'foobaz'" do
139
+ cause.to_s.should == "Failed to match sequence ('foo' 'bar') at line 1 char 4."
140
+ end
141
+
142
+ it 'has 2 nodes in error tree' do
143
+ cause.children.size.should == 1
144
+ end
145
+ end
146
+
147
+ it "parses 'foobar'" do
148
+ parslet.parse('foobar')
149
+ end
150
+
151
+ it "inspects as ('foo' 'bar')" do
152
+ parslet.inspect.should == "'foo' 'bar'"
153
+ end
154
+ end
155
+
156
+ describe "str('foo') | str('bar')" do
157
+ attr_reader :parslet
158
+
159
+ before do
160
+ @parslet = str('foo') | str('bar')
161
+ end
162
+
163
+ context "when failing on input 'baz'" do
164
+ let!(:cause) do
165
+ catch_failed_parse { parslet.parse('baz') }
166
+ end
167
+
168
+ it 'has a sensible cause' do
169
+ cause.to_s.should == "Expected one of ['foo', 'bar'] at line 1 char 1."
170
+ end
171
+
172
+ it 'has an error tree with 3 nodes' do
173
+ cause.children.size.should == 2
174
+ end
175
+ end
176
+
177
+ it "accepts 'foo'" do
178
+ parslet.parse('foo')
179
+ end
180
+
181
+ it "accepts 'bar'" do
182
+ parslet.parse('bar')
183
+ end
184
+
185
+ it "inspects as ('foo' / 'bar')" do
186
+ parslet.inspect.should == "'foo' / 'bar'"
187
+ end
188
+ end
189
+
190
+ describe "str('foo').present? (positive lookahead)" do
191
+ attr_reader :parslet
192
+
193
+ before do
194
+ @parslet = str('foo').present?
195
+ end
196
+
197
+ it "inspects as &'foo'" do
198
+ parslet.inspect.should == "&'foo'"
199
+ end
200
+
201
+ context "when fed 'foo'" do
202
+ it 'parses' do
203
+ success, = parslet.apply(src('foo'), context)
204
+ success.should == true
205
+ end
206
+
207
+ it 'does not change input position' do
208
+ source = src('foo')
209
+ parslet.apply(source, context)
210
+ source.pos.should == 0
211
+ end
212
+ end
213
+
214
+ context "when fed 'bar'" do
215
+ it 'does not parse' do
216
+ -> { parslet.parse('bar') }.should not_parse
217
+ end
218
+ end
219
+
220
+ describe '<- #parse' do
221
+ it 'returns nil' do
222
+ parslet.apply(src('foo'), context).should == [true, nil]
223
+ end
224
+ end
225
+ end
226
+
227
+ describe "str('foo').absent? (negative lookahead)" do
228
+ attr_reader :parslet
229
+
230
+ before do
231
+ @parslet = str('foo').absent?
232
+ end
233
+
234
+ it "inspects as !'foo'" do
235
+ parslet.inspect.should == "!'foo'"
236
+ end
237
+
238
+ context "when fed 'bar'" do
239
+ it 'parses' do
240
+ parslet.apply(src('bar'), context).should == [true, nil]
241
+ end
242
+
243
+ it 'does not change input position' do
244
+ source = src('bar')
245
+ parslet.apply(source, context)
246
+ source.pos.should == 0
247
+ end
248
+ end
249
+
250
+ context "when fed 'foo'" do
251
+ it 'does not parse' do
252
+ -> { parslet.parse('foo') }.should not_parse
253
+ end
254
+ end
255
+ end
256
+
257
+ describe 'non greedy matcher combined with greedy matcher (possible loop)' do
258
+ attr_reader :parslet
259
+
260
+ before do
261
+ # repeat will always succeed, since it has a minimum of 0. It will not
262
+ # modify input position in that case. absent? will, depending on
263
+ # implementation, match as much as possible and call its inner element
264
+ # again. This leads to an infinite loop. This example tests for the
265
+ # absence of that loop.
266
+ @parslet = str('foo').repeat.maybe
267
+ end
268
+
269
+ unless RUBY_ENGINE == 'opal'
270
+ it 'does not loop infinitely' do
271
+ lambda {
272
+ Timeout.timeout(1) { parslet.parse('bar') }
273
+ }.should raise_error(Parsanol::ParseFailed)
274
+ end
275
+ end
276
+ end
277
+
278
+ describe 'any' do
279
+ attr_reader :parslet
280
+
281
+ before do
282
+ @parslet = any
283
+ end
284
+
285
+ it 'matches' do
286
+ parslet.parse('.')
287
+ end
288
+
289
+ it 'consumes one char' do
290
+ source = src('foo')
291
+ parslet.apply(source, context)
292
+ source.pos.should == 1
293
+ end
294
+ end
295
+
296
+ describe 'eof behaviour' do
297
+ context "when the pattern just doesn't consume the input" do
298
+ let(:parslet) { any }
299
+
300
+ it 'fails the parse' do
301
+ cause = catch_failed_parse { parslet.parse('..') }
302
+ cause.to_s.should == "Don't know what to do with \".\" at line 1 char 2."
303
+ end
304
+ end
305
+
306
+ context "when the pattern doesn't match the input" do
307
+ let(:parslet) { (str('a')).repeat(1) }
308
+
309
+ attr_reader :exception
310
+
311
+ before do
312
+ parslet.parse('a.')
313
+ rescue StandardError => e
314
+ @exception = e
315
+ end
316
+
317
+ it 'raises Parsanol::ParseFailed' do
318
+ # ParseFailed here, because the input doesn't match the parser grammar.
319
+ exception.should be_kind_of(Parsanol::ParseFailed)
320
+ end
321
+
322
+ it 'has the correct error message' do
323
+ exception.message.should == \
324
+ 'Extra input after last repetition at line 1 char 2.'
325
+ end
326
+ end
327
+ end
328
+
329
+ describe '<- #as(name)' do
330
+ context "str('foo').as(:bar)" do
331
+ it "returns :bar => 'foo'" do
332
+ strip_positions(str('foo').as(:bar).parse('foo')).should == { bar: 'foo' }
333
+ end
334
+ end
335
+
336
+ context "match('[abc]').as(:name)" do
337
+ it "returns :name => 'b'" do
338
+ strip_positions(match('[abc]').as(:name).parse('b')).should == { name: 'b' }
339
+ end
340
+ end
341
+
342
+ context "match('[abc]').repeat.as(:name)" do
343
+ it "returns collated result ('abc')" do
344
+ strip_positions(match('[abc]').repeat.as(:name)
345
+ .parse('abc')).should == { name: 'abc' }
346
+ end
347
+ end
348
+
349
+ context "(str('a').as(:a) >> str('b').as(:b)).as(:c)" do
350
+ it 'returns a hash of hashes' do
351
+ strip_positions((str('a').as(:a) >> str('b').as(:b)).as(:c)
352
+ .parse('ab')).should == {
353
+ c: {
354
+ a: 'a',
355
+ b: 'b',
356
+ },
357
+ }
358
+ end
359
+ end
360
+
361
+ context "(str('a').as(:a) >> str('ignore') >> str('b').as(:b))" do
362
+ it "correctlies flatten (leaving out 'ignore')" do
363
+ strip_positions((str('a').as(:a) >> str('ignore') >> str('b').as(:b))
364
+ .parse('aignoreb')).should ==
365
+ {
366
+ a: 'a',
367
+ b: 'b',
368
+ }
369
+ end
370
+ end
371
+
372
+ context "(str('a') >> str('ignore') >> str('b')) (no .as(...))" do
373
+ it 'returns simply the original string' do
374
+ strip_positions((str('a') >> str('ignore') >> str('b'))
375
+ .parse('aignoreb')).should == 'aignoreb'
376
+ end
377
+ end
378
+
379
+ context "str('a').as(:a) >> str('b').as(:a)" do
380
+ attr_reader :parslet
381
+
382
+ before do
383
+ @parslet = str('a').as(:a) >> str('b').as(:a)
384
+ end
385
+
386
+ it 'issues a warning that a key is being overwritten in merge' do
387
+ expect(parslet).to receive(:warn).once
388
+ strip_positions(parslet.parse('ab')).should == { a: 'b' }
389
+ end
390
+
391
+ it "returns :a => 'b'" do
392
+ expect(parslet).to receive(:warn)
393
+
394
+ strip_positions(parslet.parse('ab')).should == { a: 'b' }
395
+ end
396
+ end
397
+
398
+ context "str('a').absent?" do
399
+ it 'returns something in merge, even though it is nil' do
400
+ strip_positions((str('a').absent? >> str('b').as(:b))
401
+ .parse('b')).should == { b: 'b' }
402
+ end
403
+ end
404
+
405
+ context "str('a').as(:a).repeat" do
406
+ it 'returns an array of subtrees' do
407
+ expect(strip_positions(str('a').as(:a).repeat.parse('aa'))).to eq([{ a: 'a' }, { a: 'a' }])
408
+ end
409
+ end
410
+ end
411
+
412
+ describe '<- #flatten(val)' do
413
+ def call(val)
414
+ dummy = str('a')
415
+ allow(dummy).to receive(:warn)
416
+ dummy.flatten(val)
417
+ end
418
+
419
+ [
420
+ # In absence of named subtrees: ----------------------------------------
421
+ # Sequence or Repetition
422
+ [[:sequence, 'a', 'b'], 'ab'],
423
+ [[:repetition, 'a', 'a'], 'aa'],
424
+
425
+ # Nested inside another node
426
+ [[:sequence, [:sequence, 'a', 'b']], 'ab'],
427
+ # Combined with lookahead (nil)
428
+ [[:sequence, nil, 'a'], 'a'],
429
+
430
+ # Including named subtrees ---------------------------------------------
431
+ # Atom: A named subtree
432
+ [{ a: 'a' }, { a: 'a' }],
433
+ # Composition of subtrees
434
+ [[:sequence, { a: 'a' }, { b: 'b' }], { a: 'a', b: 'b' }],
435
+ # Mixed subtrees :sequence of :repetition yields []
436
+ [[:sequence, [:repetition, { a: 'a' }], { a: 'a' }], [{ a: 'a' }, { a: 'a' }]],
437
+ [[:sequence, { a: 'a' }, [:repetition, { a: 'a' }]], [{ a: 'a' }, { a: 'a' }]],
438
+ [[:sequence, [:repetition, { a: 'a' }], [:repetition, { a: 'a' }]], [{ a: 'a' }, { a: 'a' }]],
439
+ # Repetition
440
+ [[:repetition, [:repetition, { a: 'a' }], [:repetition, { a: 'a' }]],
441
+ [{ a: 'a' }, { a: 'a' }]],
442
+ [[:repetition, { a: 'a' }, 'a', { a: 'a' }], [{ a: 'a' }, { a: 'a' }]],
443
+ [[:repetition, { a: 'a' }, [:repetition, { b: 'b' }]], [{ a: 'a' }]],
444
+
445
+ # Some random samples --------------------------------------------------
446
+ [[:sequence, { a: :b, b: :c }], { a: :b, b: :c }],
447
+ [[:sequence, { a: :b }, 'a', { c: :d }], { a: :b, c: :d }],
448
+ [[:repetition, { a: :b }, 'a', { c: :d }], [{ a: :b }, { c: :d }]],
449
+ [[:sequence, { a: :b }, { a: :d }], { a: :d }],
450
+ [[:sequence, { a: :b }, [:sequence, [:sequence, "\n", nil]]], { a: :b }],
451
+ [[:sequence, nil, ' '], ' '],
452
+ ].each do |input, output|
453
+ it "transforms #{input.inspect} to #{output.inspect}" do
454
+ call(input).should == output
455
+ end
456
+ end
457
+ end
458
+
459
+ describe 'combinations thereof (regression)' do
460
+ [
461
+ [(str('a').repeat >> str('b').repeat), 'aaabbb'],
462
+ ].each do |(parslet, input)|
463
+ describe "#{parslet.inspect} applied to #{input.inspect}" do
464
+ it 'parses successfully' do
465
+ parslet.parse(input)
466
+ end
467
+ end
468
+ end
469
+
470
+ [
471
+ [str('a'), "'a'"],
472
+ [(str('a') | str('b')).maybe, "('a' / 'b')?"],
473
+ [(str('a') >> str('b')).maybe, "('a' 'b')?"],
474
+ [str('a').maybe.maybe, "'a'??"],
475
+ [(str('a') >> str('b')).maybe.maybe, "('a' 'b')??"],
476
+ [(str('a') >> (str('b') | str('c'))), "'a' ('b' / 'c')"],
477
+
478
+ [str('a') >> str('b').repeat, "'a' 'b'{0, }"],
479
+ [(str('a') >> str('b')).repeat, "('a' 'b'){0, }"],
480
+ ].each do |(parslet, inspect_output)|
481
+ context "regression for #{parslet.inspect}" do
482
+ it "inspects correctly as #{inspect_output}" do
483
+ parslet.inspect.should == inspect_output
484
+ end
485
+ end
486
+ end
487
+ end
488
+ end