sexpistol 0.0.1

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