rip-parser 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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