linguify 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development do
4
+ gem "shoulda", ">= 0"
5
+ gem "bundler", "~> 1.0.0"
6
+ gem "jeweler", "~> 1.6.4"
7
+ gem "rcov", ">= 0"
8
+ gem "rspec", ">= 0"
9
+ end
10
+ gem 'sourcify'
data/Gemfile.lock ADDED
@@ -0,0 +1,45 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.3)
5
+ file-tail (1.0.7)
6
+ tins (~> 0.3)
7
+ git (1.2.5)
8
+ jeweler (1.6.4)
9
+ bundler (~> 1.0)
10
+ git (>= 1.2.5)
11
+ rake
12
+ rake (0.9.2)
13
+ rcov (0.9.11)
14
+ rspec (2.6.0)
15
+ rspec-core (~> 2.6.0)
16
+ rspec-expectations (~> 2.6.0)
17
+ rspec-mocks (~> 2.6.0)
18
+ rspec-core (2.6.4)
19
+ rspec-expectations (2.6.0)
20
+ diff-lcs (~> 1.1.2)
21
+ rspec-mocks (2.6.0)
22
+ ruby2ruby (1.3.1)
23
+ ruby_parser (~> 2.0)
24
+ sexp_processor (~> 3.0)
25
+ ruby_parser (2.3.1)
26
+ sexp_processor (~> 3.0)
27
+ sexp_processor (3.0.7)
28
+ shoulda (2.11.3)
29
+ sourcify (0.5.0)
30
+ file-tail (>= 1.0.5)
31
+ ruby2ruby (>= 1.2.5)
32
+ ruby_parser (>= 2.0.5)
33
+ sexp_processor (>= 3.0.5)
34
+ tins (0.3.1)
35
+
36
+ PLATFORMS
37
+ ruby
38
+
39
+ DEPENDENCIES
40
+ bundler (~> 1.0.0)
41
+ jeweler (~> 1.6.4)
42
+ rcov
43
+ rspec
44
+ shoulda
45
+ sourcify
data/README.md ADDED
@@ -0,0 +1,167 @@
1
+ # Linguify
2
+
3
+ Linguify is a linguistic compiler allowing you to compile and execute plain english.
4
+ And thus allows you to program in plain english provided you have the reduction rules needed.
5
+
6
+ Since the code ends up compiled like all code should be you can execute your code so amazingly fast I am in awe just to be able to write this divine text to you about it.
7
+
8
+ ## Installation
9
+
10
+ gem install linguify
11
+
12
+ ## Basic usage
13
+
14
+ require 'linguify'
15
+
16
+ reduce /all directories/ => 'directories' do
17
+ Dir.entries('.').select{ |f| f[0] != '.' && File.directory?(f) }
18
+ end
19
+
20
+ reduce /({directories:[^}]*}) recursively/ => 'directories' do |dirs|
21
+ all_dirs = dirs
22
+ Find.find(dirs) do |path|
23
+ if FileTest.directory?(path)
24
+ if File.basename(path)[0] == '.'
25
+ Find.prune # Don't look any further into this directory.
26
+ else
27
+ all_dirs << path
28
+ next
29
+ end
30
+ end
31
+ end
32
+ all_dirs
33
+ end
34
+
35
+ reduce /all files inside ({directories:[^}]*})/ => 'files' do |dirs|
36
+ dirs.map{ |f| File.new(f, "r") }
37
+ end
38
+
39
+ reduce /view ({files:[^}]*})/ => '' do |files|
40
+ files.each do |file|
41
+ pp file
42
+ end
43
+ end
44
+
45
+ "view all files inside all directories recursively".linguify.to_ruby
46
+ # => "code = lambda do
47
+ # directories_0 = Dir.entries(\".\").select { |f| ((not (f[0] == \".\")) and File.directory?(f)) }
48
+ # directories_1 = (all_dirs = directories_0
49
+ # Find.find(directories_0) do |path|
50
+ # if FileTest.directory?(path) then
51
+ # if (File.basename(path)[0] == \".\") then
52
+ # Find.prune
53
+ # else
54
+ # (all_dirs << path)
55
+ # next
56
+ # end
57
+ # end
58
+ # end
59
+ # all_dirs)
60
+ # files_2 = directories_1.map { |f| File.new(f, \"r\") }
61
+ # files_2.each { |file| pp(file) }
62
+ # end
63
+ # "
64
+
65
+ And if you simply want to execute your magnificent piece of art:
66
+
67
+ "view all files inside all directories recursively".linguify.run
68
+
69
+ Or even:
70
+
71
+ # compile once, run plenty
72
+ code = "view all files inside all directories recursively".linguify
73
+ loop do
74
+ code.run
75
+ end
76
+
77
+ ## More advanced usage
78
+
79
+ Linguify supports mixing javascript and ruby.
80
+ A typical case would be to express NOSQL queries in plain English for everyones convenience.
81
+
82
+ require 'linguify'
83
+
84
+ reduce /a possible javascript NOSQL query/ => {:to => 'query', :lang => :js} do
85
+ @db.forEach(lambda{ |record|
86
+ emit(record);
87
+ }
88
+ )
89
+ end
90
+
91
+ reduce /execute ({query:[^}]*})/ => '' do |query|
92
+ db.map query
93
+ end
94
+
95
+ "execute a possible javascript NOSQL query".linguify.to_ruby
96
+ # => "code = lambda do
97
+ # query = \"function(){\\n this.db.forEach(function(record){\\n emit(record)\\n });\\n}\"
98
+ # db.map(query)
99
+ # end
100
+ # "
101
+
102
+ The nature of Linguify's expression reduction face pragmatic programmers with a urge to inline the code the arguments represents.
103
+ Luckily Linguify has evolved to embrace such minds. Linguify is not for the general masses. It is for the mighty few pragmatics.
104
+
105
+ require 'linguify'
106
+
107
+ reduce /inlined code/ => {:to => 'code', :lang => :ruby, :inline => true} do
108
+ something.each do |foobar| # life is not worth living without psedo foobars
109
+ pp foobar
110
+ end
111
+ end
112
+
113
+ reduce /execute ({code:[^}]*})/ => '' do |code|
114
+ pp "hey mum"
115
+ code
116
+ pp "you will never know what I just did"
117
+ end
118
+
119
+ "execute inlined code".linguify.to_ruby
120
+ # => "code = lambda do
121
+ # (pp(\"hey mum\")
122
+ # (something.each { |foobar| pp(foobar) })
123
+ # pp(\"you will never know what I just did\"))
124
+ # end
125
+ # "
126
+
127
+ And you can even inline sub-expressions:
128
+
129
+ require 'linguify'
130
+
131
+ reduce /sub expression/ => {:to => 'sub_expression', :lang => :ruby, :inline => true} do
132
+ pp "this is the sub expression code"
133
+ end
134
+
135
+ reduce /({sub_expression:[^}]*}) of inlined code/ => {:to => 'code', :lang => :ruby, :inline => true} do |sub|
136
+ something.each do |foobar| # life is not worth living without psedo foobars
137
+ pp foobar
138
+ end
139
+ end
140
+
141
+ reduce /execute ({code:[^}]*})/ => '' do |code|
142
+ pp "hey mum"
143
+ code
144
+ code[:sub]
145
+ pp "you will never know what I just did"
146
+ end
147
+
148
+ "execute sub expression of inlined code".linguify.to_ruby
149
+ # => "code = lambda do
150
+ # (pp(\"hey mum\")
151
+ # (something.each { |foobar| pp(foobar) })
152
+ # pp(\"this is the sub expression code\")
153
+ # pp(\"you will never know what I just did\"))
154
+ # end
155
+ # "
156
+
157
+ ## License
158
+
159
+ (The MIT License)
160
+
161
+ Copyright (c) 2011 Patrick Hanevold
162
+
163
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
164
+
165
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
166
+
167
+ THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ gem.name = "linguify"
17
+ gem.homepage = "http://github.com/patrickhno/linguify"
18
+ gem.license = "MIT"
19
+ gem.summary = %Q{Linguify, the linguistic compiler.}
20
+ gem.description = %Q{Linguify is a linguistic compiler allowing you to compile and execute plain english.}
21
+ gem.email = "patrick.hanevold@gmail.com"
22
+ gem.authors = ["Patrick Hanevold"]
23
+ # dependencies defined in Gemfile
24
+ end
25
+ Jeweler::RubygemsDotOrgTasks.new
26
+
27
+ require 'rcov/rcovtask'
28
+ Rcov::RcovTask.new do |test|
29
+ test.libs << 'test'
30
+ test.pattern = 'test/**/test_*.rb'
31
+ test.verbose = true
32
+ test.rcov_opts << '--exclude "gems/*"'
33
+ end
34
+
35
+ task :default => :spec
36
+
37
+ require 'rake/rdoctask'
38
+ Rake::RDocTask.new do |rdoc|
39
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
40
+
41
+ rdoc.rdoc_dir = 'rdoc'
42
+ rdoc.title = "linguify #{version}"
43
+ rdoc.rdoc_files.include('README*')
44
+ rdoc.rdoc_files.include('lib/**/*.rb')
45
+ end
46
+
47
+ Dir['tasks/*.rake'].sort.each { |f| load f }
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.0
@@ -0,0 +1,116 @@
1
+ # encoding: utf-8
2
+
3
+ require 'sourcify'
4
+ require 'linguify/translators/javascript'
5
+
6
+ module Linguify
7
+
8
+ class Linguified
9
+
10
+ attr_accessor :proc, :sentence
11
+
12
+ # Lingquify a sentence
13
+ #
14
+ # * +str+ - A plain English string, or a plain English string with reductions in it.
15
+ # * +replace+ - In which "scope" to bind the code during compilation
16
+ #
17
+ def initialize str,bind
18
+
19
+ #
20
+ # reduction loop
21
+ #
22
+ @sentence = str.dup
23
+ @bind = bind
24
+ loop do
25
+ rule = find_rule(str)
26
+
27
+ reduction = rule[:proc].to_reduction :returns => rule[:result],
28
+ :lang => rule[:lang],
29
+ :inline => rule[:inline],
30
+ :regexp => rule[:match].inspect,
31
+ :args => rule[:match].match(str).to_a[1..-1]
32
+
33
+ str.gsub!(rule[:match],reduction.to_rexp)
34
+ break if /^{.*}$/ =~ str
35
+ end
36
+
37
+ @encoded = str
38
+
39
+ @merged_code = []
40
+ if /^{(?<code>.*)}$/ =~ @encoded
41
+ # successfully reduced entire string, compile it
42
+ code = Reduction::parse(code).compile
43
+
44
+ # and wrap it up
45
+ @sexy = Sexp.new(:block,
46
+ Sexp.new(:lasgn,:code, Sexp.new(:iter,
47
+ Sexp.new(:call, nil, :lambda, Sexp.new(:arglist)), nil,
48
+ Sexp.new(:block,
49
+ *code
50
+ )
51
+ )
52
+ ),
53
+ )
54
+
55
+ @@me = self
56
+ eval to_ruby(
57
+ Sexp.new(:call,
58
+ Sexp.new(:colon2, Sexp.new(:const, :Linguify), :Linguified), :trampoline, Sexp.new(:arglist, Sexp.new(:lvar, :code))
59
+ )
60
+ ),bind
61
+ raise "hell" unless @proc
62
+ else
63
+ raise "hell"
64
+ end
65
+ end
66
+
67
+ # Find a reduction rule for the string
68
+ #
69
+ # * +str+ - A plain English string, or a plain English string with reductions in it.
70
+ #
71
+ def find_rule str
72
+ found = Linguify.rules.select do |rule|
73
+ if rule[:match] =~ str
74
+ true
75
+ else
76
+ false
77
+ end
78
+ end
79
+ raise "no step definition for #{str}" if found.size == 0
80
+
81
+ found[0]
82
+ end
83
+
84
+ def to_sexp
85
+ @sexy
86
+ end
87
+
88
+ def to_ruby additional=nil
89
+ clone = Marshal.load(Marshal.dump(@sexy)) # sexy is not cleanly duplicated
90
+ clone << additional if additional
91
+ Ruby2Ruby.new.process(clone)
92
+ end
93
+
94
+ def run
95
+ begin
96
+ @proc.call
97
+ rescue Exception => e
98
+ $stderr.puts e
99
+ end
100
+ end
101
+
102
+ def register_code code
103
+ @proc = code
104
+ end
105
+
106
+ def self.trampoline code
107
+ @@me.register_code code
108
+ end
109
+
110
+ def self.cache
111
+ @@cache ||= {}
112
+ end
113
+
114
+ end
115
+
116
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+
3
+ class Proc
4
+ def to_reduction args={}
5
+ Linguify::Reduction.new(
6
+ :returns => args[:returns] || '',
7
+ :lang => args[:lang] || :ruby,
8
+ :inline => args[:inline] || false,
9
+ :location => source_location[0],
10
+ :line => source_location[1],
11
+ :regexp => args[:regexp] || //.inspect,
12
+ :args => args[:args] || [],
13
+ :sexp => self.to_sexp
14
+ )
15
+ end
16
+
17
+ def to_code collection
18
+ reduction = to_reduction
19
+
20
+ sexy = reduction.compile
21
+ code = Marshal.load(Marshal.dump(sexy.first)) # sexy is not cleanly duplicated
22
+ code.replace_variable_references!(Linguify::Replacement.new(:sexp => collection.name),:collection)
23
+ code
24
+ end
25
+ end
26
+
@@ -0,0 +1,153 @@
1
+ # encoding: utf-8
2
+
3
+ module Linguify
4
+
5
+ class Replacement
6
+ attr_reader :sexp
7
+
8
+ def initialize args
9
+ @sexp = args[:sexp]
10
+ @inline = args[:inline] || false
11
+ end
12
+
13
+ def inline?
14
+ @inline
15
+ end
16
+ end
17
+
18
+ class Reduction
19
+
20
+ attr_accessor :returns, :location, :line, :regexp, :args, :sexp, :rule_args, :from, :reduction_id, :lang, :inline, :named_args
21
+
22
+ @@reductions = []
23
+
24
+ def initialize params
25
+ @returns = params[:returns]
26
+ @lang = params[:lang]
27
+ @inline = params[:inline]
28
+ @location = params[:location]
29
+ @line = params[:line]
30
+ @regexp = params[:regexp]
31
+ @rule_args= @regexp.split(')').map{ |sub| sub.split('(')[1] }.select{ |v| v }
32
+ @args = params[:args]
33
+ @sexp = params[:sexp]
34
+ @reduction_id = @@reductions.size
35
+ determine_arguments
36
+ @@reductions << self
37
+ end
38
+
39
+ def determine_arguments
40
+ s = Marshal.load(Marshal.dump(self)) # sexp handling is not clean cut
41
+ raise "what is this?" unless s.sexp.sexp_type == :iter && s.sexp[1].sexp_type == :call && s.sexp[1][1] == nil && s.sexp[1][2] == :proc && s.sexp[1][3].sexp_type == :arglist
42
+
43
+ block_args = s.sexp[2]
44
+ if block_args
45
+ if block_args[0]==:lasgn
46
+ # single argument
47
+ args = [block_args[1]]
48
+ elsif block_args[0]==:masgn
49
+ # multiple arguments
50
+ args = block_args[1]
51
+ raise "unsupported argument type #{args}" unless args[0]==:array
52
+ args = args[1..-1].map{ |arg|
53
+ raise "unsupported argument type #{arg}" unless arg[0]==:lasgn
54
+ arg[1]
55
+ }
56
+ else
57
+ raise "unsupported argument type #{args}"
58
+ end
59
+ end
60
+ # args[] now has the symbolized argument names of the code block
61
+ @named_args = Hash[*args.zip(@args).flatten] if args
62
+ @named_args ||= {}
63
+ end
64
+
65
+ def self.parse str
66
+ if /^{(?<return>[^:]*):(?<rid>[0-9]+)}$/ =~ str
67
+ @@reductions[rid.to_i]
68
+ elsif /^(?<return>[^:]*):(?<rid>[0-9]+)$/ =~ str
69
+ @@reductions[rid.to_i]
70
+ else
71
+ raise "hell #{str}"
72
+ end
73
+ end
74
+
75
+ def compile
76
+ compile_with_return_to_var
77
+ end
78
+
79
+ # Compile self
80
+ #
81
+ # * +return_variable+ - The return variable. Can either be a symbol representing the variable name or nil to skip variable assigning.
82
+ # * +replace+ - A list of variables in need of a new unique name or replacement with inlined code
83
+ #
84
+ def compile_with_return_to_var(return_variable=nil, replace = {})
85
+ s = Marshal.load(Marshal.dump(self)) # sexp handling is not clean cut
86
+ args = @named_args.keys
87
+ # args[] now has the symbolized argument names of the code block
88
+
89
+ args_code = []
90
+ s.args.each_with_index do |arg,i|
91
+ if /^{(?<ret>[^:]*):(?<n>[0-9]+)}$/ =~ arg
92
+ # got a argument that referes to a reduction
93
+ # pseudo allocate a return variable name and compile the reduction
94
+ red = Reduction::parse(arg)
95
+ if red.lang != lang && red.lang == :js && lang == :ruby
96
+ # paste javascript code into a ruby variable
97
+ code = red.compile_with_return_to_var(nil,replace)
98
+ clone = Marshal.load(Marshal.dump(code)) # code is not cleanly duplicated
99
+ code = Sexp.new(:iter,Sexp.new(:call, nil, :lambda, Sexp.new(:arglist)), nil,
100
+ Sexp.new(:block,
101
+ *clone
102
+ )
103
+ )
104
+ code = Ruby2Js.new.process(code)
105
+ code = [Sexp.new(:lasgn, args[i], Sexp.new(:lit, code))]
106
+ args_code += code
107
+ else
108
+ raise "trying to reference #{red.lang} code in #{lang} code" if red.lang != lang
109
+ if red.inline
110
+ code = red.compile_with_return_to_var(nil,replace)
111
+ replace[args[i]] = Replacement.new(:sexp => Sexp.new(:block,*code), :inline => true)
112
+ else
113
+ code = red.compile_with_return_to_var("#{ret}_#{n}".to_sym,replace)
114
+ args_code += code
115
+ replace[args[i]] = Replacement.new(:sexp => "#{ret}_#{n}".to_sym)
116
+ end
117
+ end
118
+ elsif /^[0-9]+$/ =~ arg
119
+ # got a number argument, stuff it in a integer variable
120
+ args_code << Sexp.new(:lasgn, args[i], Sexp.new(:lit, arg.to_i))
121
+ else
122
+ raise "hell"
123
+ end
124
+ end
125
+
126
+ if return_variable
127
+ if s.sexp[3][0] == :block
128
+ code = Sexp.new(:lasgn, return_variable,
129
+ Sexp.new(:block,
130
+ *(s.sexp[3][1..-1].map{ |s| s.dup })
131
+ )
132
+ )
133
+ else
134
+ code = Sexp.new(:lasgn, return_variable, s.sexp[3].dup)
135
+ end
136
+ else
137
+ code = s.sexp[3].dup
138
+ end
139
+
140
+ replace.each do |k,v|
141
+ code.replace_variable_references!(v,k,@named_args)
142
+ end
143
+
144
+ return *args_code + [code]
145
+ end
146
+
147
+ def to_rexp
148
+ raise "hell" if returns.kind_of?(Array)
149
+ "{#{returns}:#{reduction_id}}"
150
+ end
151
+ end
152
+
153
+ end
@@ -0,0 +1,60 @@
1
+ # encoding: utf-8
2
+
3
+ class Sexp < Array
4
+
5
+ # Recurcively replace all references in a code section
6
+ #
7
+ # * +code+ - The code haystack to search and replace in
8
+ # * +replacement+ - The replacement code. Either a Sexp (containing code to inline) or a symbol
9
+ # * +needle+ - The search needle
10
+ #
11
+ def replace_variable_references!(replacement,needle,named_args="")
12
+
13
+ case sexp_type
14
+ when :lasgn
15
+ self[1]=replacement.sexp if self[1] == needle
16
+ when :lvar
17
+ if self[1] == needle
18
+ unless replacement.inline?
19
+ self[1]=replacement.sexp
20
+ end
21
+ end
22
+ when :call
23
+ self[2]=replacement.sexp if self[2] == needle
24
+ when :lvar
25
+ self[1]=replacement.sexp if self[1] == needle
26
+ end
27
+ # inlining requires complex code:
28
+ if replacement.inline? && [:iter, :block].include?(sexp_type)
29
+ # we have a inline and a block, replace any references with the sexp
30
+ self[1..-1].each_with_index do |h,i|
31
+ if h && h.kind_of?(Sexp) && h == Sexp.new(:lvar, needle)
32
+ # inline references like s(:lvar, :needle)
33
+ # indicates a call to the needle, thus code wants to inline
34
+ h[0] = replacement.sexp[0]
35
+ h[1] = replacement.sexp[1]
36
+ elsif h && h.kind_of?(Sexp) && named_args.has_key?(needle) &&
37
+ Linguify::Reduction.parse(named_args[needle]).named_args.select{ |k,v|
38
+ h == Sexp.new(:call, Sexp.new(:lvar, needle), :[], Sexp.new(:arglist, Sexp.new(:lit, k)))
39
+ }.size == 1
40
+ # code is asking for a injection of one of the argument's with:
41
+ # s(:call, s(:lvar, :needle), :[], s(:arglist, s(:lit, :argumen)))
42
+ # which in ruby looks like:
43
+ # needle[:argument]
44
+ # which again is the way we support calling arguments of the neede
45
+ arg = h[3][1][1]
46
+ sexy = Marshal.load(Marshal.dump(Linguify::Reduction.parse(Linguify::Reduction.parse(named_args[needle]).named_args[arg]).sexp)) # sexp handling is not clean cut
47
+ self[i+1] = sexy[3]
48
+ else
49
+ h.replace_variable_references!(replacement,needle,named_args) if h && h.kind_of?(Sexp)
50
+ end
51
+ end
52
+ else
53
+ self[1..-1].each do |h|
54
+ h.replace_variable_references!(replacement,needle,named_args) if h && h.kind_of?(Sexp)
55
+ end
56
+ end
57
+ end
58
+
59
+ end
60
+
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ class String
4
+ def linguify bind=binding
5
+ return Linguify::Linguified::cache[self] if Linguify::Linguified::cache[self]
6
+ Linguify::Linguified::cache[self] = Linguify::Linguified.new(self,bind)
7
+ end
8
+ end
9
+