rip-parser 0.1.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.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +15 -0
  3. data/.gitignore +9 -0
  4. data/.rspec +1 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE.md +9 -0
  8. data/README.md +13 -0
  9. data/Rakefile +1 -0
  10. data/bin/console +8 -0
  11. data/bin/setup +8 -0
  12. data/legacy/normalizer.rb +279 -0
  13. data/legacy/parser_spec.rb +999 -0
  14. data/legacy/rules.rb +250 -0
  15. data/legacy/rules_spec.rb +1700 -0
  16. data/rip-parser.gemspec +20 -0
  17. data/source/rip/parser/about.rb +9 -0
  18. data/source/rip/parser/error.rb +36 -0
  19. data/source/rip/parser/grammar.rb +23 -0
  20. data/source/rip/parser/keywords.rb +84 -0
  21. data/source/rip/parser/location.rb +47 -0
  22. data/source/rip/parser/node.rb +115 -0
  23. data/source/rip/parser/rules/assignment.rb +23 -0
  24. data/source/rip/parser/rules/binary_condition.rb +24 -0
  25. data/source/rip/parser/rules/character.rb +40 -0
  26. data/source/rip/parser/rules/class.rb +60 -0
  27. data/source/rip/parser/rules/common.rb +47 -0
  28. data/source/rip/parser/rules/date_time.rb +31 -0
  29. data/source/rip/parser/rules/expression.rb +122 -0
  30. data/source/rip/parser/rules/import.rb +23 -0
  31. data/source/rip/parser/rules/invocation.rb +15 -0
  32. data/source/rip/parser/rules/invocation_index.rb +15 -0
  33. data/source/rip/parser/rules/keyword.rb +12 -0
  34. data/source/rip/parser/rules/lambda.rb +45 -0
  35. data/source/rip/parser/rules/list.rb +18 -0
  36. data/source/rip/parser/rules/map.rb +15 -0
  37. data/source/rip/parser/rules/module.rb +29 -0
  38. data/source/rip/parser/rules/number.rb +21 -0
  39. data/source/rip/parser/rules/pair.rb +13 -0
  40. data/source/rip/parser/rules/property.rb +33 -0
  41. data/source/rip/parser/rules/range.rb +15 -0
  42. data/source/rip/parser/rules/reference.rb +16 -0
  43. data/source/rip/parser/rules/string.rb +41 -0
  44. data/source/rip/parser/rules/unit.rb +17 -0
  45. data/source/rip/parser/rules.rb +7 -0
  46. data/source/rip/parser/utilities/normalizer.rb +638 -0
  47. data/source/rip/parser.rb +24 -0
  48. data/source/rip-parser.rb +1 -0
  49. data/spec/fixtures/syntax_sample.rip +96 -0
  50. data/spec/spec_helper.rb +20 -0
  51. data/spec/support/helpers.rb +57 -0
  52. data/spec/support/parslet.rb +1 -0
  53. data/spec/support/shared_examples.rb +9 -0
  54. data/spec/unit/rip/parser/grammar_spec.rb +8 -0
  55. data/spec/unit/rip/parser/location_spec.rb +89 -0
  56. data/spec/unit/rip/parser/node_spec.rb +157 -0
  57. data/spec/unit/rip/parser/rules/assignment_spec.rb +59 -0
  58. data/spec/unit/rip/parser/rules/binary_condition_spec.rb +41 -0
  59. data/spec/unit/rip/parser/rules/character_spec.rb +29 -0
  60. data/spec/unit/rip/parser/rules/class_spec.rb +181 -0
  61. data/spec/unit/rip/parser/rules/common_spec.rb +88 -0
  62. data/spec/unit/rip/parser/rules/date_time_spec.rb +61 -0
  63. data/spec/unit/rip/parser/rules/expression_spec.rb +95 -0
  64. data/spec/unit/rip/parser/rules/import_spec.rb +64 -0
  65. data/spec/unit/rip/parser/rules/invocation_index_spec.rb +46 -0
  66. data/spec/unit/rip/parser/rules/invocation_spec.rb +46 -0
  67. data/spec/unit/rip/parser/rules/keyword_spec.rb +17 -0
  68. data/spec/unit/rip/parser/rules/lambda_spec.rb +174 -0
  69. data/spec/unit/rip/parser/rules/list_spec.rb +45 -0
  70. data/spec/unit/rip/parser/rules/map_spec.rb +36 -0
  71. data/spec/unit/rip/parser/rules/module_spec.rb +63 -0
  72. data/spec/unit/rip/parser/rules/number_spec.rb +40 -0
  73. data/spec/unit/rip/parser/rules/pair_spec.rb +25 -0
  74. data/spec/unit/rip/parser/rules/property_spec.rb +27 -0
  75. data/spec/unit/rip/parser/rules/range_spec.rb +37 -0
  76. data/spec/unit/rip/parser/rules/reference_spec.rb +43 -0
  77. data/spec/unit/rip/parser/rules/string_spec.rb +166 -0
  78. data/spec/unit/rip/parser/rules/unit_spec.rb +17 -0
  79. data/spec/unit/rip/parser_spec.rb +106 -0
  80. metadata +192 -0
@@ -0,0 +1,96 @@
1
+ # start with a comment
2
+
3
+ import "something"
4
+ import(:other)
5
+
6
+ answers = [ 42, +42, -42 ]; deserts = [ 3.14, +3.14, -3.14 ]
7
+
8
+ CAT = System.String.upper_case(:cat)
9
+
10
+ # heredoc
11
+ <<FOO
12
+
13
+ multi-line
14
+
15
+ \ttry one today!
16
+
17
+ FOO
18
+
19
+
20
+ /reg-ex/
21
+
22
+
23
+ [
24
+ `v,
25
+ `\n,
26
+ `\u0123
27
+ ]
28
+
29
+
30
+ please-dont-ever-do-this = (((((foo).bar()).baz)))
31
+
32
+ factorial = (n, m = 0) -> {
33
+ if (n.==(0)) {
34
+ m
35
+ } else {
36
+ self(n.-(1), n.+(m))
37
+ }
38
+ }
39
+
40
+ answer = 42
41
+ super-answer = factorial(answer)
42
+
43
+ :range: 0..9.map
44
+
45
+ map = { :answer: 42 }
46
+
47
+ => { -> { 42 } }
48
+ -> { :cat }
49
+
50
+ lambda = => {
51
+ (x) -> { x }
52
+ }
53
+
54
+ overload = (n) -> {
55
+ lambda(n)
56
+ }
57
+
58
+ Person = class {
59
+ aaa = 3.14
60
+
61
+ self.bbb = ~> {
62
+ @.aaa
63
+ }
64
+
65
+ @.ccc = -> {
66
+ @.name
67
+ }
68
+
69
+ @.initialize = => {
70
+ (name<String>) -> {
71
+ @.name = name
72
+ }
73
+ }
74
+
75
+ Nested = class { }
76
+ }
77
+
78
+ System = class {
79
+ String = import "./string"
80
+ }
81
+
82
+ "a#{b.c}d"
83
+ /a#{b.c}d/
84
+
85
+ rip_initial_commit = 2012-02-12T00:24:00-0500
86
+
87
+ lunch-time = 12:30:00.today()
88
+
89
+ space = 100_000km
90
+ mariana = -10_994km
91
+
92
+ foo()
93
+ list[42]
94
+ a.b.c
95
+
96
+ 1.a.+(2.b).-(3.c).*(4.d)
@@ -0,0 +1,20 @@
1
+ require 'pry'
2
+
3
+ require_relative '../source/rip-parser'
4
+
5
+ pattern = Pathname.new(__dir__).join('support/**/*.rb')
6
+ Pathname.glob(pattern).each(&method(:require))
7
+
8
+ RSpec.configure do |config|
9
+ config.run_all_when_everything_filtered = true
10
+ config.filter_run :focus => true
11
+ config.filter_run_excluding :blur => true
12
+
13
+ config.expect_with :rspec do |c|
14
+ c.syntax = :expect
15
+ end
16
+
17
+ config.order = 'random'
18
+
19
+ config.color = true
20
+ end
@@ -0,0 +1,57 @@
1
+ module RSpecHelpers
2
+ def profile_parslet(rip, parslet = :lines)
3
+ binding.pry
4
+
5
+ result = RubyProf.profile do
6
+ parser(rip).send(parslet).parse_tree
7
+ end
8
+
9
+ result.eliminate_methods!([
10
+ /Array/,
11
+ /Class/,
12
+ /Enumerable/,
13
+ /Fixnum/,
14
+ /Hash/,
15
+ /Kernel/,
16
+ /Module/,
17
+ /Object/,
18
+ /Proc/,
19
+ /Regexp/,
20
+ /String/,
21
+ /Symbol/
22
+ ])
23
+
24
+ tree = RubyProf::CallInfoPrinter.new(result)
25
+ tree.print(STDOUT)
26
+ end
27
+
28
+ def location_for(options = {})
29
+ origin = options[:origin] || Pathname.pwd
30
+ offset = options[:offset] || 0
31
+ line = options[:line] || 1
32
+ column = options[:column] || 1
33
+ Rip::Parser::Location.new(origin, offset, line, column)
34
+ end
35
+
36
+ # http://apidock.com/rails/String/strip_heredoc
37
+ def strip_heredoc(string)
38
+ indent = string.scan(/^[ \t]*(?=\S)/).min.size
39
+ string.gsub(/^[ \t]{#{indent}}/, '')
40
+ end
41
+
42
+ def clean_inspect(ast)
43
+ ast.inspect
44
+ .gsub(/@\d+/, '')
45
+ .gsub('\\"', '\'')
46
+ .gsub(/:0x[0-9a-f]+/, '')
47
+ .gsub('Rip::Nodes::', '')
48
+ .gsub('Rip::Utilities::Location ', '')
49
+ .gsub(/ @location=\#\<([^>]+)>/, '@\1')
50
+ end
51
+ end
52
+
53
+ RSpec.configure do |config|
54
+ config.include RSpecHelpers
55
+
56
+ config.extend RSpecHelpers
57
+ end
@@ -0,0 +1 @@
1
+ require 'parslet/rig/rspec'
@@ -0,0 +1,9 @@
1
+ shared_examples 'debug methods' do
2
+ context '.type_instance.to_s' do
3
+ specify { expect(type_instance.to_s).to eq(type_to_s) }
4
+ end
5
+
6
+ context '#to_s' do
7
+ specify { expect(instance.to_s).to eq(instance_to_s) }
8
+ end
9
+ end
@@ -0,0 +1,8 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Rip::Parser::Grammar do
4
+ describe '.parse' do
5
+ context 'syntax error' do
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Rip::Parser::Location do
4
+ subject { location }
5
+ let(:location) { Rip::Parser::Location.new(:rspec, 0, 1, 0) }
6
+
7
+ describe '#==' do
8
+ it 'glosses over superficial differences' do
9
+ expect(location).to eq(Rip::Parser::Location.new(:rspec, 0, 1, 0))
10
+ end
11
+
12
+ it 'notices important difference in source' do
13
+ expect(location).not_to eq(location_for(:origin => :cucumber))
14
+ end
15
+
16
+ it 'notices important differences in position' do
17
+ expect(location).not_to eq(location_for(:offset => 3))
18
+ end
19
+ end
20
+
21
+ describe '#to_s' do
22
+ specify { expect(subject.to_s).to eq('rspec:1:0(0)') }
23
+
24
+ context 'in another file' do
25
+ let(:location) { Rip::Parser::Location.new('lib/rip.rip', 47, 8, 3, 5) }
26
+
27
+ specify { expect(subject.to_s).to eq('lib/rip.rip:8:3(47..51)') }
28
+ end
29
+ end
30
+
31
+ describe '#add_character' do
32
+ let(:new_location) { Rip::Parser::Location.new(:rspec, 5, 1, 5) }
33
+
34
+ it 'returns a new location offset by specified characters' do
35
+ expect(location.add_character(5)).to eq(new_location)
36
+ end
37
+ end
38
+
39
+ describe '#add_line' do
40
+ let(:new_location) { Rip::Parser::Location.new(:rspec, 2, 3, 2) }
41
+
42
+ it 'returns a new location offset by specified lines' do
43
+ expect(location.add_line(2)).to eq(new_location)
44
+ end
45
+ end
46
+
47
+ describe '.from_slice' do
48
+ let(:location) { Rip::Parser::Location.from_slice(:rspec, slice) }
49
+
50
+ let(:parser) do
51
+ Class.new(Parslet::Parser) do
52
+ root :lines
53
+
54
+ rule(:lines) { line.repeat }
55
+ rule(:line) { (as | bs).as(:line) >> eol }
56
+
57
+ rule(:as) { a.repeat(3) }
58
+ rule(:bs) { b.repeat(3) }
59
+
60
+ rule(:a) { str('a').as(:a) }
61
+ rule(:b) { str('b').as(:b) }
62
+
63
+ rule(:eol) { str("\n") }
64
+ end.new
65
+ end
66
+
67
+ let(:slice) { slices[4] }
68
+ let(:slices) { tree.map(&:values).flatten.map(&:values).flatten }
69
+
70
+ let(:source) do
71
+ strip_heredoc(<<-SOURCE)
72
+ aaa
73
+ bbb
74
+ SOURCE
75
+ end
76
+
77
+ let(:tree) { parser.parse(source) }
78
+
79
+ specify { expect(location.origin).to eq(:rspec) }
80
+
81
+ specify { expect(location.offset).to eq(5) }
82
+
83
+ specify { expect(location.line).to eq(2) }
84
+
85
+ specify { expect(location.column).to eq(2) }
86
+
87
+ specify { expect(location.length).to eq(1) }
88
+ end
89
+ end
@@ -0,0 +1,157 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Rip::Parser::Node do
4
+ let(:location) { Rip::Parser::Location.new(:rspec, 0, 1, 0) }
5
+ let(:node) { Rip::Parser::Node.new(location: location, type: :test, answer: 42) }
6
+
7
+ describe '#==' do
8
+ specify { expect(node).to eq(Rip::Parser::Node.new(location: location, type: :test, answer: 42)) }
9
+ specify { expect(node).to eq(location: location, type: :test, answer: 42) }
10
+ end
11
+
12
+ describe '#[]' do
13
+ context 'responding to location' do
14
+ specify { expect(node[:location]).to eq(location) }
15
+ end
16
+
17
+ context 'symbol key' do
18
+ specify { expect(node[:answer]).to eq(42) }
19
+ end
20
+
21
+ context 'string key' do
22
+ specify { expect(node['answer']).to eq(42) }
23
+ end
24
+
25
+ context 'missing key' do
26
+ specify { expect(node[:other]).to be(nil) }
27
+ end
28
+ end
29
+
30
+ describe '#each' do
31
+ specify do
32
+ expect do |x|
33
+ node.each(&x)
34
+ end.to yield_successive_args([ :location, location ], [ :type, :test ], [ :answer, 42 ])
35
+ end
36
+ end
37
+
38
+ describe '#key?' do
39
+ specify { expect(node.key?(:answer)).to be(true) }
40
+ specify { expect(node.key?(:foo)).to be(false) }
41
+
42
+ specify { expect(node.key?('answer')).to be(true) }
43
+ end
44
+
45
+ describe '#keys' do
46
+ specify { expect(node.keys).to match_array([ :answer ]) }
47
+ end
48
+
49
+ describe '#length' do
50
+ specify { expect(node.length).to eq(node.location.length) }
51
+ end
52
+
53
+ describe '#merge' do
54
+ let(:other_location) { Rip::Parser::Location.new(:rspec, 1, 2, 1) }
55
+ let(:other) { Rip::Parser::Node.new(location: other_location, type: :other_test, foo: :bar) }
56
+
57
+ specify { expect(node.merge(foo: :bar)).to be_a(Rip::Parser::Node) }
58
+
59
+ specify { expect(node.merge(answer: :bar).to_h).to eq(location: location, type: :test, answer: :bar) }
60
+ specify { expect(node.merge(type: :baz, foo: :bar).to_h).to eq(location: location, type: :baz, answer: 42, foo: :bar) }
61
+
62
+ specify { expect(node.merge(other).to_h).to eq(location: location, type: :other_test, answer: 42, foo: :bar) }
63
+ end
64
+
65
+ describe '#s_expression' do
66
+ let(:tree) { Rip::Parser::Node.new(location: location, type: :root, children: [ node ]) }
67
+
68
+ let(:expected) do
69
+ {
70
+ type: :root,
71
+ children: [
72
+ {
73
+ type: :test,
74
+ answer: 42
75
+ }
76
+ ]
77
+ }
78
+ end
79
+
80
+ specify { expect(tree.s_expression).to eq(expected) }
81
+ specify { expect(tree.s_expression).to be_a(Hash) }
82
+
83
+ specify { expect(tree.s_expression.keys).to eq([ :type, :children ]) }
84
+
85
+ specify { expect(tree.s_expression[:children].first).to be_a(Hash) }
86
+ end
87
+
88
+ describe '#to_h' do
89
+ specify { expect(node.to_h.keys).to eq([ :location, :type, :answer ]) }
90
+
91
+ context 'nested tree' do
92
+ let(:tree) { Rip::Parser::Node.new(location: location, type: :root, other: node) }
93
+
94
+ let(:expected) do
95
+ {
96
+ location: location,
97
+ type: :root,
98
+ other: {
99
+ location: location,
100
+ type: :test,
101
+ answer: 42
102
+ }
103
+ }
104
+ end
105
+
106
+ specify { expect(tree.to_h).to eq(expected) }
107
+ specify { expect(tree.to_h[:other]).to be_a(Hash) }
108
+ end
109
+ end
110
+
111
+ describe '.new' do
112
+ let(:tree) { Rip::Parser::Node.new(location: location, type: :root, nested: { location: location, type: :nested, foo: :bar }) }
113
+
114
+ specify { expect(tree.nested).to be_a(Rip::Parser::Node) }
115
+ specify { expect(tree.nested.foo).to eq(:bar) }
116
+ end
117
+
118
+ context 'dynamic message lookup' do
119
+ specify { expect(node).to respond_to(:answer) }
120
+ specify { expect(node).to_not respond_to(:foo) }
121
+
122
+ specify { expect(node.answer).to eq(42) }
123
+ specify { expect { node.foo }.to raise_error(NoMethodError) }
124
+ end
125
+
126
+ context 'nesting' do
127
+ let(:root) { Rip::Parser::Node.new(location: location, type: :root, other: node) }
128
+
129
+ specify { expect(root.other).to eq(node) }
130
+ specify { expect(root.other.answer).to eq(42) }
131
+
132
+ specify { expect(root).to be_root }
133
+ specify { expect(root.type).to eq(:root) }
134
+
135
+ specify { expect(root).to respond_to(:root?) }
136
+ specify { expect(root).to respond_to(:test?) }
137
+
138
+ context 'nested collection' do
139
+ let(:nodes) do
140
+ [
141
+ Rip::Parser::Node.new(location: location, type: :nested, aaa: 111),
142
+ Rip::Parser::Node.new(location: location, type: :special, bbb: 222),
143
+ Rip::Parser::Node.new(location: location, type: :nested, ccc: 333)
144
+ ]
145
+ end
146
+
147
+ let(:root) { Rip::Parser::Node.new(location: location, type: :root, others: nodes) }
148
+
149
+ let(:filtered) { nodes.select(&:special?) }
150
+
151
+ specify { expect(root.others.sample).to be_a(Rip::Parser::Node) }
152
+
153
+ specify { expect(filtered.count).to eq(1) }
154
+ specify { expect(filtered.first.type).to eq(:special) }
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Rip::Parser::Rules::Assignment do
4
+ class AssignmentParser
5
+ include Rip::Parser::Rules::Assignment
6
+ include Rip::Parser::Rules::Module
7
+ end
8
+
9
+ let(:parser) { AssignmentParser.new }
10
+
11
+ describe '#reference_assignment' do
12
+ subject { parser.reference_assignment }
13
+
14
+ it { should_not parse('a.b = 42') }
15
+
16
+ it do
17
+ should parse('answer = 42').as(
18
+ lhs: { reference: 'answer' },
19
+ location: '=',
20
+ rhs: {
21
+ expression_chain: { integer: '42' }
22
+ }
23
+ )
24
+ end
25
+
26
+ it do
27
+ should parse('answer = foo.bar').as(
28
+ lhs: { reference: 'answer' },
29
+ location: '=',
30
+ rhs: {
31
+ expression_chain: [
32
+ { reference: 'foo' },
33
+ { location: '.', property_name: 'bar' }
34
+ ]
35
+ }
36
+ )
37
+ end
38
+ end
39
+
40
+ describe '#property_assignment' do
41
+ subject { parser.property_assignment }
42
+
43
+ it { should_not parse('a = 42') }
44
+
45
+ it do
46
+ should parse('a.b = 42').as(
47
+ lhs: {
48
+ object: { reference: 'a' },
49
+ location: '.',
50
+ property_name: 'b'
51
+ },
52
+ location: '=',
53
+ rhs: {
54
+ expression_chain: { integer: '42' }
55
+ }
56
+ )
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Rip::Parser::Rules::BinaryCondition do
4
+ class BinaryConditionParser
5
+ include Rip::Parser::Rules::BinaryCondition
6
+ include Rip::Parser::Rules::Module
7
+ end
8
+
9
+ let(:parser) { BinaryConditionParser.new }
10
+
11
+ describe '#binary_condition' do
12
+ subject { parser.binary_condition }
13
+
14
+ it do
15
+ should parse('if (result) { :yes } else { :no }').as(
16
+ if: 'if',
17
+ condition: { expression_chain: { reference: 'result' } },
18
+ consequence: {
19
+ expression_chain: {
20
+ location: ':',
21
+ string: [
22
+ { character: 'y' },
23
+ { character: 'e' },
24
+ { character: 's' }
25
+ ]
26
+ }
27
+ },
28
+ else: 'else',
29
+ alternative: {
30
+ expression_chain: {
31
+ location: ':',
32
+ string: [
33
+ { character: 'n' },
34
+ { character: 'o' }
35
+ ]
36
+ }
37
+ }
38
+ )
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Rip::Parser::Rules::Character do
4
+ class CharacterParser
5
+ include Rip::Parser::Rules::Character
6
+ end
7
+
8
+ let(:parser) { CharacterParser.new }
9
+
10
+ describe '#character' do
11
+ subject { parser.character }
12
+
13
+ it { should parse('`c').as(location: '`', character: 'c') }
14
+ it { should parse('`\n').as(location: '`', escape_location: '\\', escape_special: 'n') }
15
+ end
16
+
17
+ describe '#escape_sequence' do
18
+ subject { parser.escape_sequence }
19
+
20
+ it { should parse('\u1234').as(escape_location: '\\', escape_unicode: '1234') }
21
+
22
+ described_class::SPECIAL_ESCAPES.each do |_, sequence|
23
+ it { should parse("\\#{sequence}").as(escape_location: '\\', escape_special: sequence) }
24
+ end
25
+
26
+ it { should parse('\w').as(escape_location: '\\', escape_any: 'w') }
27
+ it { should_not parse('\ ') }
28
+ end
29
+ end