sexpistol 0.0.1

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/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Aaron Gough (http://thingsaaronmade.com/)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,71 @@
1
+ = Sexpistol
2
+
3
+ Sexpistol is a simple library for parsing S-Expressions in Ruby. Sexpistol takes an S-Expression and turns it into a native Ruby data structure made up of nested sets of arrays.
4
+
5
+ === Example
6
+
7
+ (define test (lambda () (
8
+ (print "blah")
9
+ (print 1)
10
+ (print 9.01)
11
+ (print (+ 10 12 13))
12
+ )))
13
+
14
+ would be parsed by Sexpistol like so:
15
+
16
+ [:define, :test, [:lambda, [], [
17
+ [:print, "blah"],
18
+ [:print, 1],
19
+ [:print, 9.01],
20
+ [:print, [:"+", 10, 12, 13]]
21
+ ]]]
22
+
23
+ === Type mappings
24
+
25
+ Sexpistol supports all of the standard datatypes and converts them directly to their Ruby equivalents:
26
+
27
+ * Lists (a b c)
28
+ * Integers (1 2 3)
29
+ * Floats (1.0 2.0 42.9)
30
+ * Strings ("\t\"Hello world!\"\n")
31
+ * Symbols (symbol Symbol __symbol__ symbo_l symbol? symbol!)
32
+
33
+ Sexpistol also supports mapping the Ruby keyword literals (nil, true, false) to their native Ruby types, although this is disabled by default for compatibility. To enable it use `@parser.ruby_keyword_literals = true`, eg:
34
+
35
+ @parser = Sexpistol.new
36
+ @parser.parse_string("nil false true")
37
+ #=> [:nil, :false, :true]
38
+
39
+ @parser.ruby_keyword_literals = true
40
+ @parser.parse_string("nil false true")
41
+ #=> [nil, false, true]
42
+
43
+ === Installation
44
+
45
+ For convenience Sexpistol is packaged as a RubyGem, to install it simply enter the following at your command line:
46
+
47
+ gem install sexpistol
48
+
49
+ === Usage
50
+
51
+ # Create a new parser instance
52
+ @parser = Sexpistol.new
53
+
54
+ # Parse a string
55
+ ast = @parser.parse_string("(string (to (parse)))")
56
+ #=> [:string, [:to, [:parse]]]
57
+
58
+ # Change the representation
59
+ ast[1][0] = :is
60
+ ast[1][1][0] = :parsed
61
+ #=> [:string, [:is, [:parsed]]]
62
+
63
+ # Turn the array structure back into an S-Expression
64
+ @parser.to_sexp( ast )
65
+ #=> "( string ( is ( parsed ) ) )"
66
+
67
+ === Author & Credits
68
+
69
+ Author:: {Aaron Gough}[mailto:aaron@aarongough.com]
70
+
71
+ Copyright (c) 2010 {Aaron Gough}[http://thingsaaronmade.com/] ({thingsaaronmade.com}[http://thingsaaronmade.com/]), released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,42 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ begin
9
+ require 'jeweler'
10
+ Jeweler::Tasks.new do |gemspec|
11
+ gemspec.name = "sexpistol"
12
+ gemspec.summary = "An S-Expression Parser Library for Ruby"
13
+ gemspec.description = "Sexpistol is an easy-to-use S-Expression parser for Ruby. It is fast and has no dependencies."
14
+ gemspec.email = "aaron@aarongough.com"
15
+ gemspec.homepage = "http://github.com/aarongough/sexpistol"
16
+ gemspec.authors = ["Aaron Gough"]
17
+ gemspec.rdoc_options << '--line-numbers' << '--inline-source'
18
+ gemspec.extra_rdoc_files = ['README.rdoc', 'MIT-LICENSE']
19
+ end
20
+ rescue LoadError
21
+ puts "Jeweler not available. Install it with: gem install jeweler"
22
+ end
23
+
24
+
25
+ desc 'Test sexpistol.'
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'lib/*.rb'
28
+ t.libs << 'test'
29
+ t.pattern = 'test/**/*_test.rb'
30
+ t.verbose = true
31
+ end
32
+
33
+
34
+ desc 'Generate documentation for sexpistol.'
35
+ Rake::RDocTask.new(:rdoc) do |rdoc|
36
+ rdoc.rdoc_dir = 'rdoc'
37
+ rdoc.title = 'Koi'
38
+ rdoc.options << '--line-numbers' << '--inline-source'
39
+ rdoc.rdoc_files.include('README.rdoc')
40
+ rdoc.rdoc_files.include('lib/**/*.rb')
41
+ rdoc.rdoc_files.include('app/**/*.rb')
42
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,137 @@
1
+ # This class contains our logic for parsing
2
+ # S-Expressions. They are turned into a
3
+ # native Ruby representation like:
4
+ # [:def, :something [:lambda, [:a], [:do_something]]]
5
+ class Sexpistol
6
+ attr_accessor :ruby_keyword_literals
7
+
8
+ def initialize
9
+ @ruby_keyword_literals = false
10
+ end
11
+
12
+ # Parse a string containing an S-Expression into a
13
+ # nested set of Ruby arrays
14
+ def parse_string( string )
15
+ string.gsub!("(", " ( ")
16
+ string.gsub!(")", " ) ")
17
+ string_array = string.split
18
+ tokens = process_tokens( string_array )
19
+ check_tokens( tokens )
20
+ structure( tokens )
21
+ end
22
+
23
+ # Check and array of tokens to make sure that the number
24
+ # of open and closing parentheses match
25
+ def check_tokens( tokens )
26
+ unless( (tokens.reject {|x| x == "("}).length == (tokens.reject {|x| x == ")"}).length)
27
+ raise Exception, "Invalid S-Expression. The number of opening and closing parentheses do not match."
28
+ end
29
+ end
30
+
31
+ # Iterate over an array of strings and turn each
32
+ # item into it's relevant token. ie: "string" -> :string, "1" -> 1
33
+ def process_tokens( token_array )
34
+ tokens = []
35
+ token_array.each do |t|
36
+ if(@ruby_keyword_literals)
37
+ tokens << nil and next if(is_nil?(t))
38
+ tokens << true and next if(is_true?(t))
39
+ tokens << false and next if(is_false?(t))
40
+ end
41
+ tokens << t and next if(is_paren?(t))
42
+ tokens << t.to_f and next if( is_float?(t))
43
+ tokens << t.to_i and next if( is_integer?(t))
44
+ tokens << t.to_sym and next if( is_identifier?(t) || is_symbol?(t))
45
+ tokens << eval(t) and next if( is_string_literal?(t))
46
+ raise "\nUnrecognized token: #{t}\n"
47
+ end
48
+ return tokens
49
+ end
50
+
51
+ # Iterate over a flat array of tokens and turn it
52
+ # a nested set of arrays by detecting '(' and ')'
53
+ def structure( token_array, offset = 0, internal = false )
54
+ program = []
55
+ while(offset < token_array.length)
56
+ if( token_array[offset] == "(" )
57
+ offset, array = structure( token_array, offset + 1, true )
58
+ program << array
59
+ elsif( token_array[offset] == ")" )
60
+ break
61
+ else
62
+ program << token_array[offset]
63
+ end
64
+ offset += 1
65
+ end
66
+ if(internal)
67
+ return [offset, program]
68
+ else
69
+ return program
70
+ end
71
+ end
72
+
73
+ # Test to see whether or not a string represents the 'nil' literal
74
+ def is_nil?( string )
75
+ true if(string == "nil")
76
+ end
77
+
78
+ # Test to see whether or not a string represents the 'true' literal
79
+ def is_true?( string )
80
+ true if(string == "true")
81
+ end
82
+
83
+ # Test to see whether or not a string represents the 'false' literal
84
+ def is_false?( string )
85
+ true if(string == "false")
86
+ end
87
+
88
+ # Test to see whether a string represents a parentheses
89
+ def is_paren?( string )
90
+ is_match?( string, /[\(\)]+/ )
91
+ end
92
+
93
+ # Test to see whether or not a string represents an integer
94
+ def is_integer?( string )
95
+ is_match?( string, /[\-\+]?[0-9]+/ )
96
+ end
97
+
98
+ # Test to see whether or not a string represents a float
99
+ def is_float?( string )
100
+ is_match?( string, /[\-\+]?[0-9]+\.[0-9]+/ )
101
+ end
102
+
103
+ # Test to see whether or not a string represents an identifier
104
+ def is_identifier?( string )
105
+ is_match?( string, /_*[a-zA-Z]+[a-zA-Z0-9_]*\??\!?/ )
106
+ end
107
+
108
+ # Test to see whether or not a string represents a string literal
109
+ def is_string_literal?( string )
110
+ is_match?( string, /".*"/)
111
+ end
112
+
113
+ # Test to see whether or not a string represents a symbol
114
+ def is_symbol?( string )
115
+ is_match?( string, /[\!\*\^=\/\+\-]+/ )
116
+ end
117
+
118
+ # Convert a set of nested arrays back into an S-Expression
119
+ def to_sexp( data )
120
+ mapped = data.map do |item|
121
+ if( item.is_a?(Array))
122
+ to_sexp(item)
123
+ else
124
+ item.to_s
125
+ end
126
+ end
127
+ "( " + mapped.join(" ") + " )"
128
+ end
129
+
130
+ private
131
+
132
+ def is_match?( string, pattern )
133
+ match = string.match(pattern)
134
+ return false unless match
135
+ match[0].length == string.length
136
+ end
137
+ end
data/lib/sexpistol.rb ADDED
@@ -0,0 +1 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'sexpistol', 'sexpistol.rb'))
@@ -0,0 +1,21 @@
1
+ module Test::Unit
2
+ # Used to fix a minor minitest/unit incompatibility in flexmock
3
+ AssertionFailedError = Class.new(StandardError)
4
+
5
+ class TestCase
6
+
7
+ def self.test(name, &block)
8
+ test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
9
+ defined = instance_method(test_name) rescue false
10
+ raise "#{test_name} is already defined in #{self}" if defined
11
+ if block_given?
12
+ define_method(test_name, &block)
13
+ else
14
+ define_method(test_name) do
15
+ flunk "No implementation provided for #{name}"
16
+ end
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+
4
+ require_files = []
5
+ require_files << File.join(File.dirname(__FILE__), '..', 'lib', 'sexpistol.rb')
6
+ require_files.concat Dir[File.join(File.dirname(__FILE__), 'setup', '*.rb')]
7
+
8
+ require_files.each do |file|
9
+ require File.expand_path(file)
10
+ end
@@ -0,0 +1,30 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper.rb'))
2
+
3
+ class FloatLiteralTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @parser = Sexpistol.new
7
+ end
8
+
9
+ test "should parse sexp containing an implicitly positive float literal" do
10
+ ast = @parser.parse_string("10.00")
11
+ assert_equal [10.00], ast
12
+ end
13
+
14
+ test "should parse sexp containing an explicitly positive float literal" do
15
+ ast = @parser.parse_string("+910.00")
16
+ assert_equal [910.00], ast
17
+ end
18
+
19
+ test "should parse sexp containing an explicitly negative float literal" do
20
+ ast = @parser.parse_string("-10.00")
21
+ assert_equal [-10.00], ast
22
+ end
23
+
24
+ test "should parse sexp containing a large float literal" do
25
+ ast = @parser.parse_string("1.0000127829")
26
+ assert_equal [1.0000127829], ast
27
+ end
28
+
29
+
30
+ end
@@ -0,0 +1,24 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper.rb'))
2
+
3
+ class IntegerLiteralTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @parser = Sexpistol.new
7
+ end
8
+
9
+ test "should parse sexp containing an implicitly positive integer literal" do
10
+ ast = @parser.parse_string("10")
11
+ assert_equal [10], ast
12
+ end
13
+
14
+ test "should parse sexp containing an explicitly positive integer literal" do
15
+ ast = @parser.parse_string("+910")
16
+ assert_equal [910], ast
17
+ end
18
+
19
+ test "should parse sexp containing an explicitly negative integer literal" do
20
+ ast = @parser.parse_string("-10")
21
+ assert_equal [-10], ast
22
+ end
23
+
24
+ end
@@ -0,0 +1,45 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper.rb'))
2
+
3
+ class RubyKeywordLiteralsTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @parser = Sexpistol.new
7
+ end
8
+
9
+ test "should parse nil as literal" do
10
+ @parser.ruby_keyword_literals = true
11
+ ast = @parser.parse_string('nil')
12
+ assert_equal [nil], ast
13
+ end
14
+
15
+ test "should not parse nil as literal" do
16
+ @parser.ruby_keyword_literals = false
17
+ ast = @parser.parse_string('nil')
18
+ assert_equal [:nil], ast
19
+ end
20
+
21
+ test "should parse true as literal" do
22
+ @parser.ruby_keyword_literals = true
23
+ ast = @parser.parse_string('true')
24
+ assert_equal [true], ast
25
+ end
26
+
27
+ test "should not parse true as literal" do
28
+ @parser.ruby_keyword_literals = false
29
+ ast = @parser.parse_string('true')
30
+ assert_equal [:true], ast
31
+ end
32
+
33
+ test "should parse false as literal" do
34
+ @parser.ruby_keyword_literals = true
35
+ ast = @parser.parse_string('false')
36
+ assert_equal [false], ast
37
+ end
38
+
39
+ test "should notparse false as literal" do
40
+ @parser.ruby_keyword_literals = false
41
+ ast = @parser.parse_string('false')
42
+ assert_equal [:false], ast
43
+ end
44
+
45
+ end
@@ -0,0 +1,29 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper.rb'))
2
+
3
+ class StringLiteralTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @parser = Sexpistol.new
7
+ end
8
+
9
+ test "should parse empty string literal" do
10
+ ast = @parser.parse_string('""')
11
+ assert_equal [""], ast
12
+ end
13
+
14
+ test "should parse string literal" do
15
+ ast = @parser.parse_string('"test"')
16
+ assert_equal ["test"], ast
17
+ end
18
+
19
+ test "should parse string literal containing escaped quotes" do
20
+ ast = @parser.parse_string('"te\"st"')
21
+ assert_equal ["te\"st"], ast
22
+ end
23
+
24
+ test "should parse string literal containing escaped characters" do
25
+ ast = @parser.parse_string('"\n\t\r"')
26
+ assert_equal ["\n\t\r"], ast
27
+ end
28
+
29
+ end
@@ -0,0 +1,20 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper.rb'))
2
+
3
+ class RubyKeywordLiteralsTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @parser = Sexpistol.new
7
+ end
8
+
9
+ test "should create nested set of arrays from s-expression" do
10
+ ast = @parser.parse_string('(this (is (an (s_expression))))')
11
+ assert_equal [[:this, [:is, [:an, [:s_expression]]]]], ast
12
+ end
13
+
14
+ test "should raise error on broken s-expression" do
15
+ assert_raises Exception do
16
+ ast = @parser.parse_string('(this (is (an (s_expression) too)')
17
+ end
18
+ end
19
+
20
+ end
@@ -0,0 +1,49 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper.rb'))
2
+
3
+ class SymbolTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @parser = Sexpistol.new
7
+ end
8
+
9
+ test "should parse simple symbol" do
10
+ ast = @parser.parse_string("test")
11
+ assert_equal [:test], ast
12
+ end
13
+
14
+ test "should parse symbol with trailing exclamation mark" do
15
+ ast = @parser.parse_string("test!")
16
+ assert_equal [:test!], ast
17
+ end
18
+
19
+ test "should parse symbol with trailing question mark" do
20
+ ast = @parser.parse_string("test?")
21
+ assert_equal [:test?], ast
22
+ end
23
+
24
+ test "should parse symbol containing underscores" do
25
+ ast = @parser.parse_string("te__st")
26
+ assert_equal [:te__st], ast
27
+ end
28
+
29
+ test "should parse symbol with leading underscores" do
30
+ ast = @parser.parse_string("__test")
31
+ assert_equal [:__test], ast
32
+ end
33
+
34
+ test "should parse symbol with trailing underscores" do
35
+ ast = @parser.parse_string("test__")
36
+ assert_equal [:test__], ast
37
+ end
38
+
39
+ test "should parse CamelCase symbol" do
40
+ ast = @parser.parse_string("TestSymbol")
41
+ assert_equal [:TestSymbol], ast
42
+ end
43
+
44
+ test "should parse complex symbol" do
45
+ ast = @parser.parse_string("__TestSymbol_TEST__?")
46
+ assert_equal [:__TestSymbol_TEST__?], ast
47
+ end
48
+
49
+ end
@@ -0,0 +1,15 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper.rb'))
2
+
3
+ class ToSexpTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @parser = Sexpistol.new
7
+ end
8
+
9
+ test "should convert nested arrays back into an S-Expression" do
10
+ ast = [:string, [:is, [:parsed]]]
11
+ sexp = @parser.to_sexp(ast)
12
+ assert_equal "( string ( is ( parsed ) ) )", sexp
13
+ end
14
+
15
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sexpistol
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Aaron Gough
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-10-02 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: Sexpistol is an easy-to-use S-Expression parser for Ruby. It is fast and has no dependencies.
22
+ email: aaron@aarongough.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - MIT-LICENSE
29
+ - README.rdoc
30
+ files:
31
+ - MIT-LICENSE
32
+ - README.rdoc
33
+ - Rakefile
34
+ - VERSION
35
+ - lib/sexpistol.rb
36
+ - lib/sexpistol/sexpistol.rb
37
+ - test/setup/test_unit_extensions.rb
38
+ - test/test_helper.rb
39
+ - test/unit/float_literal_test.rb
40
+ - test/unit/integer_literal_test.rb
41
+ - test/unit/ruby_keyword_literals_test.rb
42
+ - test/unit/string_literal_test.rb
43
+ - test/unit/structure_test.rb
44
+ - test/unit/symbol_test.rb
45
+ - test/unit/to_sexp_test.rb
46
+ has_rdoc: true
47
+ homepage: http://github.com/aarongough/sexpistol
48
+ licenses: []
49
+
50
+ post_install_message:
51
+ rdoc_options:
52
+ - --charset=UTF-8
53
+ - --line-numbers
54
+ - --inline-source
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ segments:
63
+ - 0
64
+ version: "0"
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ requirements: []
74
+
75
+ rubyforge_project:
76
+ rubygems_version: 1.3.7
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: An S-Expression Parser Library for Ruby
80
+ test_files:
81
+ - test/setup/test_unit_extensions.rb
82
+ - test/test_helper.rb
83
+ - test/unit/float_literal_test.rb
84
+ - test/unit/integer_literal_test.rb
85
+ - test/unit/ruby_keyword_literals_test.rb
86
+ - test/unit/string_literal_test.rb
87
+ - test/unit/structure_test.rb
88
+ - test/unit/symbol_test.rb
89
+ - test/unit/to_sexp_test.rb