indentation-parser 0.0.1 → 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.
- data/README.md +46 -8
- data/indentation-parser.gemspec +7 -6
- data/lib/indentation-parser.rb +40 -23
- data/lib/indentation-parser/handlers.rb +8 -4
- data/lib/indentation-parser/version.rb +1 -1
- data/spec/readme_example_spec.rb +68 -5
- data/spec/simple_spec.rb +10 -11
- data/spec/universe_spec.rb +7 -7
- metadata +5 -5
data/README.md
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
-
|
1
|
+
_This is not complete yet! More to come in the next weeks, so stay tuned._
|
2
2
|
|
3
|
-
# Indentation-Parser
|
3
|
+
# Indentation-Parser
|
4
4
|
|
5
|
-
|
5
|
+
[](http://travis-ci.org/ssmm/indentation-parser)
|
6
|
+
[](https://codeclimate.com/github/ssmm/indentation-parser)
|
7
|
+
|
8
|
+
Parses source code that defines context by indentation.
|
6
9
|
|
7
10
|
## How
|
8
11
|
|
@@ -37,13 +40,12 @@ You write a parser like so:
|
|
37
40
|
|
38
41
|
```ruby
|
39
42
|
parser = IndentationParser.new do |p|
|
40
|
-
p.default do |parent,
|
43
|
+
p.default do |parent, source|
|
41
44
|
node = OpenStruct.new
|
42
45
|
parent.send("#{source}=", node)
|
43
46
|
node
|
44
47
|
end
|
45
|
-
|
46
|
-
p.on /([^ ]+) : (.+)/ do |parent, indentation, source, captures|
|
48
|
+
p.on /([^ ]+) : (.+)/ do |parent, source, captures|
|
47
49
|
node = captures[2]
|
48
50
|
parent.send("#{captures[1]}=", node)
|
49
51
|
node
|
@@ -51,7 +53,7 @@ parser = IndentationParser.new do |p|
|
|
51
53
|
end
|
52
54
|
```
|
53
55
|
|
54
|
-
Then you read your special syntax from a file
|
56
|
+
Then you read your special syntax from a file and parse it:
|
55
57
|
|
56
58
|
```ruby
|
57
59
|
source = IO.read("path/to/file")
|
@@ -60,4 +62,40 @@ output = parser.read(source, OpenStruct.new).value
|
|
60
62
|
puts output.this.is.an.example
|
61
63
|
puts output.this.is.the.second.one
|
62
64
|
puts output.this.serves.as.another.example
|
63
|
-
```
|
65
|
+
```
|
66
|
+
|
67
|
+
# Details
|
68
|
+
|
69
|
+
## Hooks
|
70
|
+
|
71
|
+
The following hooks are available:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
p.on /regex/ do |parent, source, captures|
|
75
|
+
#...
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
p.default do |parent, source|
|
81
|
+
#...
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
p.on_leaf /regex/ do |parent, source, captures|
|
87
|
+
#...
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
p.on_leaf do |parent, source|
|
93
|
+
#...
|
94
|
+
end
|
95
|
+
```
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
p.as_a_child_of Type do |parent, source|
|
99
|
+
#...
|
100
|
+
end
|
101
|
+
```
|
data/indentation-parser.gemspec
CHANGED
@@ -2,18 +2,19 @@
|
|
2
2
|
require File.expand_path('../lib/indentation-parser/version', __FILE__)
|
3
3
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
|
+
gem.name = "indentation-parser"
|
6
|
+
gem.version = Indentation::Parser::VERSION
|
7
|
+
|
5
8
|
gem.authors = ["Samuel Müller"]
|
6
9
|
gem.email = ["mueller.samu@gmail.com"]
|
7
|
-
gem.description = %q{indentation
|
8
|
-
gem.summary =
|
9
|
-
gem.homepage = ""
|
10
|
+
gem.description = %q{Parses source code that defines context by indentation.}
|
11
|
+
gem.summary = gem.description
|
12
|
+
gem.homepage = "https://github.com/ssmm/indentation-parser"
|
10
13
|
|
11
14
|
gem.files = `git ls-files`.split($\)
|
12
15
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
-
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
-
gem.name = "indentation-parser"
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
15
17
|
gem.require_paths = ["lib"]
|
16
|
-
gem.version = Indentation::Parser::VERSION
|
17
18
|
|
18
19
|
gem.add_development_dependency "rspec", "~> 2.11"
|
19
20
|
end
|
data/lib/indentation-parser.rb
CHANGED
@@ -18,43 +18,60 @@ class IndentationParser
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def read text, root_value
|
21
|
-
|
22
21
|
root = IndentationParser::RootNode.new
|
23
|
-
|
24
22
|
root.set_value root_value
|
25
|
-
|
26
23
|
node_stack = [root]
|
27
24
|
|
28
25
|
text.each_line do |line|
|
29
26
|
line.chomp!
|
30
27
|
next if line.length == 0 || line =~ /^\s*$/
|
28
|
+
|
31
29
|
indentation, source = parse_line line
|
32
30
|
new_node = IndentationParser::Node.new source, indentation
|
31
|
+
previous_node = node_stack.last
|
33
32
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
node_stack.push new_node
|
45
|
-
elsif new_node.indentation() - 1 > lastone.indentation #too large indentation -> raise an error
|
33
|
+
if new_node.indentation() - 1 == previous_node.indentation
|
34
|
+
#the current node is indented to the previous node
|
35
|
+
handle_by_one_indentation previous_node, new_node, node_stack
|
36
|
+
|
37
|
+
elsif new_node.indentation == previous_node.indentation
|
38
|
+
#the current node is on the same level as the previous node
|
39
|
+
handle_same_indentation new_node, node_stack
|
40
|
+
|
41
|
+
elsif new_node.indentation() - 1 > previous_node.indentation
|
42
|
+
#too large indentation -> raise an error
|
46
43
|
raise "ou neei"
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end
|
53
|
-
node_stack.last.add new_node
|
54
|
-
node_stack.push new_node
|
44
|
+
|
45
|
+
else
|
46
|
+
#indentation is less than previous node.
|
47
|
+
#pop everything from stack until parent is found
|
48
|
+
handle_less_indentation previous_node, new_node, node_stack
|
55
49
|
end
|
56
50
|
end
|
57
51
|
handle_leaf node_stack.last
|
58
52
|
root
|
59
53
|
end
|
54
|
+
|
55
|
+
def handle_by_one_indentation previous_node, new_node, node_stack
|
56
|
+
previous_node.add new_node
|
57
|
+
handle_node previous_node unless previous_node.is_a? RootNode
|
58
|
+
node_stack.push new_node
|
59
|
+
end
|
60
|
+
|
61
|
+
def handle_same_indentation new_node, node_stack
|
62
|
+
leaf = node_stack.pop
|
63
|
+
handle_leaf leaf
|
64
|
+
node_stack.last.add new_node
|
65
|
+
node_stack.push new_node
|
66
|
+
end
|
67
|
+
|
68
|
+
def handle_less_indentation previous_node, new_node, node_stack
|
69
|
+
leaf = node_stack.pop
|
70
|
+
handle_leaf leaf
|
71
|
+
(previous_node.indentation - new_node.indentation).times do
|
72
|
+
node_stack.pop
|
73
|
+
end
|
74
|
+
node_stack.last.add new_node
|
75
|
+
node_stack.push new_node
|
76
|
+
end
|
60
77
|
end
|
@@ -1,9 +1,13 @@
|
|
1
1
|
class IndentationParser
|
2
2
|
|
3
|
+
def call_handler block, node
|
4
|
+
block.call(node.parent.value, node.source)
|
5
|
+
end
|
6
|
+
|
3
7
|
def execute_regex_handler node, regex, block
|
4
8
|
captures = regex.match node.source
|
5
9
|
if captures
|
6
|
-
node_value = block.call(node.parent.value, node.
|
10
|
+
node_value = block.call(node.parent.value, node.source, captures)
|
7
11
|
node.set_value node_value
|
8
12
|
return true
|
9
13
|
end
|
@@ -13,7 +17,7 @@ class IndentationParser
|
|
13
17
|
def execute_child_of_handler node
|
14
18
|
handler = @child_of_handlers[node.parent.value.class]
|
15
19
|
if handler
|
16
|
-
node_value = handler
|
20
|
+
node_value = call_handler handler, node
|
17
21
|
node.set_value node_value
|
18
22
|
return true
|
19
23
|
end
|
@@ -36,7 +40,7 @@ class IndentationParser
|
|
36
40
|
|
37
41
|
handled = try_to_handle @node_handlers, node
|
38
42
|
if not handled and @default
|
39
|
-
node_value = @default
|
43
|
+
node_value = call_handler @default, node if @default
|
40
44
|
node.set_value node_value
|
41
45
|
end
|
42
46
|
end
|
@@ -44,7 +48,7 @@ class IndentationParser
|
|
44
48
|
def handle_leaf node
|
45
49
|
handled = try_to_handle @leaf_handlers, node
|
46
50
|
if not handled and @on_leaf
|
47
|
-
node_value = @on_leaf
|
51
|
+
node_value = call_handler @on_leaf, node
|
48
52
|
node.set_value node_value
|
49
53
|
else
|
50
54
|
handle_node node
|
data/spec/readme_example_spec.rb
CHANGED
@@ -4,13 +4,12 @@ require 'ostruct'
|
|
4
4
|
describe IndentationParser do
|
5
5
|
it "does what is written in the readme" do
|
6
6
|
parser = IndentationParser.new do |p|
|
7
|
-
p.default do |parent,
|
7
|
+
p.default do |parent, source|
|
8
8
|
node = OpenStruct.new
|
9
9
|
parent.send("#{source}=", node)
|
10
10
|
node
|
11
11
|
end
|
12
|
-
|
13
|
-
p.on /([^ ]+) : (.+)/ do |parent, indentation, source, captures|
|
12
|
+
p.on /([^ ]+) : (.+)/ do |parent, source, captures|
|
14
13
|
node = captures[2]
|
15
14
|
parent.send("#{captures[1]}=", node)
|
16
15
|
node
|
@@ -24,6 +23,70 @@ describe IndentationParser do
|
|
24
23
|
output.this.is.an.example.should eq "First example"
|
25
24
|
output.this.is.the.second.one.should eq "Second example"
|
26
25
|
output.this.serves.as.another.example.should eq "Third example"
|
27
|
-
|
28
26
|
end
|
29
|
-
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
# ##### First block
|
31
|
+
|
32
|
+
# ```ruby
|
33
|
+
# p.default do |parent, indentation, source|
|
34
|
+
# node = OpenStruct.new
|
35
|
+
# parent.send("#{source}=", node)
|
36
|
+
# node
|
37
|
+
# end
|
38
|
+
# ```
|
39
|
+
|
40
|
+
# This defines what the parser does with a line of code when no other hook is defined. The
|
41
|
+
# parameters you get from the parser inside your block are:
|
42
|
+
|
43
|
+
# - The `parent` node. This is the object you have set for an already parsed line.
|
44
|
+
# - The `source`, basically the whole line the parser currently evaluates without indentation.
|
45
|
+
|
46
|
+
# *Now, step by step:*
|
47
|
+
|
48
|
+
# We define a new `OpenStruct` instance and store it in a variable called `node`.
|
49
|
+
|
50
|
+
# Since we know that all parent objects are going to be `OpenStruct`s too, we set an attribute on
|
51
|
+
# it. The name of the attribute is given by the `source` parameter.
|
52
|
+
|
53
|
+
# Last but not least, we return the node. **This is very important!** In
|
54
|
+
# order to be able to pass the `parent` parameter to the block, the parser maintains an internal
|
55
|
+
# node structure. Only if you pass the node as a return value, the parser can store it there!
|
56
|
+
|
57
|
+
# ##### Second block
|
58
|
+
|
59
|
+
# ```ruby
|
60
|
+
# p.on /([^ ]+) : (.+)/ do |parent, indentation, source, captures|
|
61
|
+
# node = captures[2]
|
62
|
+
# parent.send("#{captures[1]}=", node)
|
63
|
+
# node
|
64
|
+
# end
|
65
|
+
# ```
|
66
|
+
|
67
|
+
# The regular expression `/([^ ]+) : (.+)/` will match with a text that has the format
|
68
|
+
# `"text_without_spaces : Any text"`. Every time the parser comes along a line of code which
|
69
|
+
# matches this expression, it will execute the block you provide. There is an additional parameter
|
70
|
+
# you can use in this block:
|
71
|
+
# - The `captures`, the result of matching the
|
72
|
+
# [regular expression](http://www.ruby-doc.org/core-1.9.3/Regexp.html) to the source.
|
73
|
+
|
74
|
+
# *Step by step:*
|
75
|
+
|
76
|
+
# We assign the second capture, which, with the example source given, will be `"First example"` in
|
77
|
+
# the very first case, to a local variable called `node`.
|
78
|
+
|
79
|
+
# Again, at this point, we know that our parent is an `OpenStruct`. We define an attribute on it,
|
80
|
+
# with the name taken from our captures object. This is what happens for each of the three cases:
|
81
|
+
|
82
|
+
# ```ruby
|
83
|
+
# #parent.send("#{captures[1]}=", node) translates to
|
84
|
+
# parent.example = "First example"
|
85
|
+
# parent.one = "Second example"
|
86
|
+
# parent.example = "Third example"
|
87
|
+
# ```
|
88
|
+
|
89
|
+
# Actually, the last line of the second block is not required, since there will not be any child
|
90
|
+
# to this node, so there is no block that would need the parent object as a parameter. You can
|
91
|
+
# return it anyways, just for the sake of thoroughness.
|
92
|
+
|
data/spec/simple_spec.rb
CHANGED
@@ -3,16 +3,16 @@ require 'indentation-parser'
|
|
3
3
|
describe IndentationParser do
|
4
4
|
it "parses indented files" do
|
5
5
|
parser = IndentationParser.new do |p|
|
6
|
-
p.default do |parent,
|
6
|
+
p.default do |parent, source|
|
7
7
|
node = {}
|
8
8
|
parent[source.to_sym] = node
|
9
9
|
node
|
10
10
|
end
|
11
11
|
|
12
|
-
p.on_leaf do |parent,
|
13
|
-
|
14
|
-
parent[
|
15
|
-
|
12
|
+
p.on_leaf do |parent, source|
|
13
|
+
parent[:leafs] = Array.new unless parent[:leafs]
|
14
|
+
parent[:leafs] << source
|
15
|
+
source
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
@@ -22,22 +22,21 @@ describe IndentationParser do
|
|
22
22
|
|
23
23
|
expected_hash = {
|
24
24
|
:this => {
|
25
|
-
:structure => {},
|
26
25
|
:is => {
|
27
26
|
:crazy => {
|
28
|
-
:
|
27
|
+
:leafs => ["and"]
|
29
28
|
}
|
30
29
|
},
|
31
|
-
:
|
30
|
+
:leafs => ["structure", "weird"]
|
32
31
|
},
|
33
32
|
:and => {
|
34
33
|
:does => {
|
35
34
|
:not => {
|
36
|
-
:
|
35
|
+
:leafs => ["make"]
|
37
36
|
},
|
38
|
-
:
|
37
|
+
:leafs => ["any"]
|
39
38
|
},
|
40
|
-
:
|
39
|
+
:leafs => ["sense"]
|
41
40
|
}
|
42
41
|
}
|
43
42
|
|
data/spec/universe_spec.rb
CHANGED
@@ -10,39 +10,39 @@ describe IndentationParser do
|
|
10
10
|
it "parses a .universe file" do
|
11
11
|
parser = IndentationParser.new do |p|
|
12
12
|
|
13
|
-
p.on /planet = ([a-zA-Z]+)/ do |parent,
|
13
|
+
p.on /planet = ([a-zA-Z]+)/ do |parent, source, captures|
|
14
14
|
planet = Planet.new captures[1]
|
15
15
|
planet.continents = []
|
16
16
|
parent.planets << planet
|
17
17
|
planet
|
18
18
|
end
|
19
19
|
|
20
|
-
p.on /continent = ([a-zA-Z]+)/ do |parent,
|
20
|
+
p.on /continent = ([a-zA-Z]+)/ do |parent, source, captures|
|
21
21
|
continent = Continent.new captures[1]
|
22
22
|
continent.countries = []
|
23
23
|
parent.continents << continent
|
24
24
|
continent
|
25
25
|
end
|
26
26
|
|
27
|
-
p.on /country = ([a-zA-Z]+)/ do |parent,
|
27
|
+
p.on /country = ([a-zA-Z]+)/ do |parent, source, captures|
|
28
28
|
country = Country.new captures[1]
|
29
29
|
parent.countries << country
|
30
30
|
country
|
31
31
|
end
|
32
32
|
|
33
|
-
p.on /population = ([0-9]+)/ do |parent,
|
33
|
+
p.on /population = ([0-9]+)/ do |parent, source, captures|
|
34
34
|
parent.population = captures[1].to_i
|
35
35
|
end
|
36
36
|
|
37
|
-
p.on /currency = ([a-zA-Z]+)/ do |parent,
|
37
|
+
p.on /currency = ([a-zA-Z]+)/ do |parent, source, captures|
|
38
38
|
parent.currency = captures[1]
|
39
39
|
end
|
40
40
|
|
41
|
-
p.on /specialities/ do |parent,
|
41
|
+
p.on /specialities/ do |parent, source, captures|
|
42
42
|
parent.specialities = []
|
43
43
|
end
|
44
44
|
|
45
|
-
p.as_a_child_of Array do |parent,
|
45
|
+
p.as_a_child_of Array do |parent, source|
|
46
46
|
parent << source
|
47
47
|
end
|
48
48
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: indentation-parser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-07-
|
12
|
+
date: 2012-07-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -27,7 +27,7 @@ dependencies:
|
|
27
27
|
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: '2.11'
|
30
|
-
description: indentation
|
30
|
+
description: Parses source code that defines context by indentation.
|
31
31
|
email:
|
32
32
|
- mueller.samu@gmail.com
|
33
33
|
executables: []
|
@@ -52,7 +52,7 @@ files:
|
|
52
52
|
- spec/readme_example_spec.rb
|
53
53
|
- spec/simple_spec.rb
|
54
54
|
- spec/universe_spec.rb
|
55
|
-
homepage:
|
55
|
+
homepage: https://github.com/ssmm/indentation-parser
|
56
56
|
licenses: []
|
57
57
|
post_install_message:
|
58
58
|
rdoc_options: []
|
@@ -75,7 +75,7 @@ rubyforge_project:
|
|
75
75
|
rubygems_version: 1.8.24
|
76
76
|
signing_key:
|
77
77
|
specification_version: 3
|
78
|
-
summary: indentation
|
78
|
+
summary: Parses source code that defines context by indentation.
|
79
79
|
test_files:
|
80
80
|
- spec/material/an.example
|
81
81
|
- spec/material/test.mylang
|