klam 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.rspec +0 -0
  4. data/.travis.yml +8 -0
  5. data/Gemfile +1 -0
  6. data/LICENSE.txt +19 -0
  7. data/PROGRESS.asciidoc +105 -0
  8. data/README.asciidoc +11 -0
  9. data/Rakefile +5 -0
  10. data/klam.gemspec +28 -0
  11. data/lib/klam.rb +16 -0
  12. data/lib/klam/compilation_stages.rb +4 -0
  13. data/lib/klam/compilation_stages/convert_freezes_to_lambdas.rb +17 -0
  14. data/lib/klam/compilation_stages/convert_lexical_variables.rb +79 -0
  15. data/lib/klam/compilation_stages/convert_partial_applications_to_lambdas.rb +35 -0
  16. data/lib/klam/compilation_stages/convert_self_tail_calls_to_loops.rb +147 -0
  17. data/lib/klam/compilation_stages/curry_abstraction_applications.rb +33 -0
  18. data/lib/klam/compilation_stages/emit_ruby.rb +232 -0
  19. data/lib/klam/compilation_stages/kl_to_internal_representation.rb +21 -0
  20. data/lib/klam/compilation_stages/make_abstractions_monadic.rb +26 -0
  21. data/lib/klam/compilation_stages/make_abstractions_variadic.rb +23 -0
  22. data/lib/klam/compilation_stages/simplify_boolean_operations.rb +74 -0
  23. data/lib/klam/compilation_stages/strip_type_declarations.rb +24 -0
  24. data/lib/klam/compiler.rb +63 -0
  25. data/lib/klam/cons.rb +18 -0
  26. data/lib/klam/converters.rb +4 -0
  27. data/lib/klam/converters/.list.rb.swp +0 -0
  28. data/lib/klam/converters/list.rb +29 -0
  29. data/lib/klam/environment.rb +61 -0
  30. data/lib/klam/error.rb +4 -0
  31. data/lib/klam/lexer.rb +185 -0
  32. data/lib/klam/primitives.rb +4 -0
  33. data/lib/klam/primitives/arithmetic.rb +49 -0
  34. data/lib/klam/primitives/assignments.rb +13 -0
  35. data/lib/klam/primitives/boolean_operations.rb +22 -0
  36. data/lib/klam/primitives/error_handling.rb +19 -0
  37. data/lib/klam/primitives/generic_functions.rb +19 -0
  38. data/lib/klam/primitives/lists.rb +23 -0
  39. data/lib/klam/primitives/streams.rb +32 -0
  40. data/lib/klam/primitives/strings.rb +58 -0
  41. data/lib/klam/primitives/symbols.rb +16 -0
  42. data/lib/klam/primitives/time.rb +19 -0
  43. data/lib/klam/primitives/vectors.rb +26 -0
  44. data/lib/klam/reader.rb +46 -0
  45. data/lib/klam/template.rb +38 -0
  46. data/lib/klam/variable.rb +21 -0
  47. data/lib/klam/variable_generator.rb +12 -0
  48. data/lib/klam/version.rb +3 -0
  49. data/spec/functional/application_spec.rb +94 -0
  50. data/spec/functional/atoms_spec.rb +56 -0
  51. data/spec/functional/extensions/do_spec.rb +22 -0
  52. data/spec/functional/primitives/assignments_spec.rb +38 -0
  53. data/spec/functional/primitives/boolean_operations_spec.rb +133 -0
  54. data/spec/functional/primitives/error_handling_spec.rb +22 -0
  55. data/spec/functional/primitives/generic_functions_spec.rb +82 -0
  56. data/spec/functional/tail_call_optimization_spec.rb +71 -0
  57. data/spec/spec_helper.rb +27 -0
  58. data/spec/unit/klam/compilation_stages/convert_lexical_variables_spec.rb +58 -0
  59. data/spec/unit/klam/compilation_stages/convert_self_tail_calls_to_loops_spec.rb +33 -0
  60. data/spec/unit/klam/compilation_stages/curry_abstraction_applications_spec.rb +19 -0
  61. data/spec/unit/klam/compilation_stages/make_abstractions_variadic_spec.rb +12 -0
  62. data/spec/unit/klam/converters/list_spec.rb +57 -0
  63. data/spec/unit/klam/lexer_spec.rb +149 -0
  64. data/spec/unit/klam/primitives/arithmetic_spec.rb +153 -0
  65. data/spec/unit/klam/primitives/boolean_operations_spec.rb +39 -0
  66. data/spec/unit/klam/primitives/error_handling_spec.rb +19 -0
  67. data/spec/unit/klam/primitives/lists_spec.rb +49 -0
  68. data/spec/unit/klam/primitives/strings_spec.rb +53 -0
  69. data/spec/unit/klam/primitives/symbols_spec.rb +19 -0
  70. data/spec/unit/klam/primitives/time_spec.rb +16 -0
  71. data/spec/unit/klam/primitives/vectors_spec.rb +55 -0
  72. data/spec/unit/klam/reader_spec.rb +47 -0
  73. data/spec/unit/klam/template_spec.rb +28 -0
  74. data/spec/unit/klam/variable_spec.rb +22 -0
  75. data/spec/unit/klam/version_spec.rb +7 -0
  76. metadata +225 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d6bfb59d34a33d476f312e82821a8daa9c23a766
4
+ data.tar.gz: 1e17a5d167569095f128e551504e48ccfc5c3f84
5
+ SHA512:
6
+ metadata.gz: f2adf161608625ab5b3a7c7d38cb406df8152d98a55b85fa7d77f338c543f7c21a98df1923d507b52e863a2aeb47f3672dce37b18e3c28cc6226e1664b036b97
7
+ data.tar.gz: 2949ef6dcb1086f138bdd8059cf8419de98eca82c2054754244050511147c6d1119bda3969397e0b436b8b1a49dde962a470d1d5f299cb1e146b7cdcba9e46cd
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ klam-*.gem
data/.rspec ADDED
File without changes
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.0
6
+ - 2.1.1
7
+ - jruby-1.7.17
8
+ - rbx
data/Gemfile ADDED
@@ -0,0 +1 @@
1
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2014 Greg Spurrier
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/PROGRESS.asciidoc ADDED
@@ -0,0 +1,105 @@
1
+ Klam Implementation Progress
2
+ ============================
3
+
4
+ This document tracks the progress of Klam towards a feature-complete 1.0.0
5
+ release.
6
+
7
+ Implemented
8
+ -----------
9
+
10
+ Language Features
11
+ ~~~~~~~~~~~~~~~~~
12
+ * Reader
13
+ * Environment
14
+ ** Global assignment namespace
15
+ ** Global function namespace
16
+ * Code Generation
17
+ ** Atoms
18
+ ** Abstractions
19
+ ** Functions
20
+ ** Special forms (see marked primimitives below)
21
+ ** Application
22
+ *** Functions and abstractions:
23
+ **** Full application
24
+ **** Partial application
25
+ **** Currying
26
+ ** Self tail-call optimization
27
+
28
+ Primitives
29
+ ~~~~~~~~~~
30
+ As defined in
31
+ http://www.shenlanguage.org/learn-shen/shendoc.htm#The%20Primitive%20Functions%20of%20K%20Lambda[The
32
+ Primitive Functions of Kl]:
33
+
34
+ * Boolean Operations
35
+ ** +if+ (special form and normal function)
36
+ ** +and+ (special form and normal function)
37
+ ** +or+ (special form and normal function)
38
+ ** +cond+ (special form)
39
+ * Symbols
40
+ ** +intern+
41
+ * Strings
42
+ ** +pos+
43
+ ** +tlstr+
44
+ ** +cn+
45
+ ** +str+
46
+ ** +string?+
47
+ ** +n\->string+
48
+ ** +string\->n+
49
+ * Assignments
50
+ ** +set+
51
+ ** +value+
52
+ * Lists
53
+ ** +cons+
54
+ ** +hd+
55
+ ** +tl+
56
+ ** +cons?+
57
+ * Error Handling
58
+ ** +simple-error+
59
+ ** +trap-error+ (special form)
60
+ ** +error-to-string+
61
+ * Generic Functions
62
+ ** +defun+ (special form)
63
+ ** +lambda+ (special form)
64
+ ** +let+ (special form)
65
+ ** +=+
66
+ ** +eval-kl+
67
+ ** +freeze+ (special form)
68
+ ** +type+ (special form)
69
+ * Vectors
70
+ ** +absvector+
71
+ ** +address\->+
72
+ ** +\<-address+
73
+ ** +absvector?+
74
+ * Streams and I/O
75
+ ** +write-byte+
76
+ ** +read-byte+
77
+ ** +open+
78
+ ** +close+
79
+ * Time
80
+ ** +get-time+
81
+ * Arithmetic
82
+ ** +++
83
+ ** +-+
84
+ ** +*+
85
+ ** +/+
86
+ ** +>+
87
+ ** +<+
88
+ ** +>=+
89
+ ** +\<=+
90
+ ** +number?+
91
+
92
+ Ruby Interoperation
93
+ ~~~~~~~~~~~~~~~~~~~
94
+ * Ruby \<\-> Kl converters
95
+ ** Array \<\-> List
96
+
97
+ Not Yet Implemented
98
+ -------------------
99
+
100
+ Ruby Interoperation
101
+ ~~~~~~~~~~~~~~~~~~~
102
+ * Invoking Kl functions from Ruby
103
+ * Invoking Ruby functions from Kl
104
+ * Ruby \<\-> Kl converters
105
+ ** Array \<\-> Absvector
data/README.asciidoc ADDED
@@ -0,0 +1,11 @@
1
+ Klam
2
+ ====
3
+
4
+ Klam is a Ruby implementation of Kl, the small Lisp on top of which the
5
+ http://www.shenlanguage.org[Shen] programming language is implemented.
6
+
7
+ image:https://travis-ci.org/gregspurrier/klam.png?branch=master["Build Status", link="https://travis-ci.org/gregspurrier/klam"]
8
+
9
+ License
10
+ -------
11
+ Klam is Copyright (C) 2014-2015 Greg Spurrier. It is distributed under the terms of the MIT License. See link:LICENSE.txt[] for the details.
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ RSpec::Core::RakeTask.new(:spec)
4
+
5
+ task :default => :spec
data/klam.gemspec ADDED
@@ -0,0 +1,28 @@
1
+ lib = File.expand_path('../lib/', __FILE__)
2
+ $:.unshift lib unless $:.include?(lib)
3
+
4
+ require 'klam/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'klam'
8
+ s.version = Klam::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.license = 'MIT'
11
+ s.authors = ['Greg Spurrier']
12
+ s.email = ['greg@sourcematters.org']
13
+ s.homepage = 'https://github.com/gregspurrier/klam'
14
+ s.summary = %q{Klam is a Ruby implementation of the Kl.}
15
+ s.description = %q{Klam is a Ruby implementation of Kl, the small Lisp on top of which the Shen programming language is implemented.}
16
+
17
+ s.required_ruby_version = '>= 1.9.3'
18
+
19
+ s.add_development_dependency 'rake', '~> 10.4', '>= 10.4'
20
+ s.add_development_dependency 'rspec', '~> 3.1', '>= 3.1.0'
21
+ s.add_development_dependency 'rspec-autotest', '~> 1.0', '>= 1.0.0'
22
+ s.add_development_dependency 'ZenTest', '~> 4.11', '>= 4.11'
23
+
24
+ git_files = `git ls-files`.split("\n") rescue ''
25
+ s.files = git_files
26
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
27
+ s.require_paths = ['lib']
28
+ end
data/lib/klam.rb ADDED
@@ -0,0 +1,16 @@
1
+ module Klam
2
+ end
3
+
4
+ require 'klam/version'
5
+ require 'klam/error'
6
+ require 'klam/template'
7
+ require 'klam/variable'
8
+ require 'klam/variable_generator'
9
+ require 'klam/cons'
10
+ require 'klam/primitives'
11
+ require 'klam/converters'
12
+ require 'klam/compilation_stages'
13
+ require 'klam/compiler'
14
+ require 'klam/environment'
15
+ require 'klam/lexer'
16
+ require 'klam/reader'
@@ -0,0 +1,4 @@
1
+ this_dir = File.dirname(__FILE__)
2
+ Dir[File.join(this_dir, 'compilation_stages', '*.rb')].each do |file|
3
+ require file
4
+ end
@@ -0,0 +1,17 @@
1
+ module Klam
2
+ module CompilationStages
3
+ module ConvertFreezesToLambdas
4
+ def convert_freezes_to_lambdas(sexp)
5
+ if sexp.instance_of?(Array)
6
+ if sexp[0] == :freeze
7
+ [:lambda, [], convert_freezes_to_lambdas(sexp[1])]
8
+ else
9
+ sexp.map { |form| convert_freezes_to_lambdas(form) }
10
+ end
11
+ else
12
+ sexp
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,79 @@
1
+ module Klam
2
+ module CompilationStages
3
+ # Convert Lexical Variables
4
+ #
5
+ # Lexically bound variable names are symbols in Kl. In Ruby, variable and
6
+ # parameter names are represented differently than symbol literals. The
7
+ # latter are indicated with a leading ':' (e.g., :foo) and allow an
8
+ # extended set of characters when using the :"foo-bar" literal syntax.
9
+ # Ruby variables have no such provision for using additional characters.
10
+ #
11
+ # Therefore, if lexical variables remain as symbols, the emission of Ruby
12
+ # code for symbols would be complicated by the need to differentiate
13
+ # between symbol literals and lexical variables and to transform Kl
14
+ # variable names to legal Ruby variable names. Instead, this stage converts
15
+ # lexical variables to instances of Klam::Variable. This is essentially
16
+ # alpha conversion and the names of Klam::Variable are generated to be
17
+ # locally unique and using only allowed Ruby variable names.
18
+ #
19
+ # Alpha conversion also avoids potential problems when the let primitive is
20
+ # used to re-bind an already bound lexical var.
21
+ module ConvertLexicalVariables
22
+ def convert_lexical_variables(sexp)
23
+ convert_lexical_vars(sexp, {})
24
+ end
25
+
26
+ private
27
+
28
+ def convert_lexical_vars(sexp, var_map)
29
+ if sexp.instance_of? Array
30
+ case sexp[0]
31
+ when :defun
32
+ convert_lexical_vars_defun(sexp, var_map)
33
+ when :lambda
34
+ convert_lexical_vars_lambda(sexp, var_map)
35
+ when :let
36
+ convert_lexical_vars_let(sexp, var_map)
37
+ else
38
+ sexp.map { |form| convert_lexical_vars(form, var_map) }
39
+ end
40
+ else
41
+ var_map[sexp] || sexp
42
+ end
43
+ end
44
+
45
+ def convert_lexical_vars_defun(sexp, var_map)
46
+ rator, name, params, expr = sexp
47
+ var_map = extend_var_map(var_map, params)
48
+
49
+ params = params.map { |p| var_map[p] }
50
+ expr = convert_lexical_vars(expr, var_map)
51
+ [rator, name, params, expr]
52
+ end
53
+
54
+ def convert_lexical_vars_lambda(sexp, var_map)
55
+ rator, params, expr = sexp
56
+ var_map = extend_var_map(var_map, params)
57
+
58
+ params = params.map { |p| var_map[p] }
59
+ expr = convert_lexical_vars(expr, var_map)
60
+ [rator, params, expr]
61
+ end
62
+
63
+ def convert_lexical_vars_let(sexp, var_map)
64
+ rator, var_sym, value_expr, expr = sexp
65
+ extended_var_map = extend_var_map(var_map, [var_sym])
66
+
67
+ value_expr = convert_lexical_vars(value_expr, var_map)
68
+ expr = convert_lexical_vars(expr, extended_var_map)
69
+ [rator, extended_var_map[var_sym], value_expr, expr]
70
+ end
71
+
72
+ def extend_var_map(var_map, syms)
73
+ new_var_map = Hash.new { |_, k| var_map[k] }
74
+ syms.each { |sym| new_var_map[sym] = fresh_variable }
75
+ new_var_map
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,35 @@
1
+ module Klam
2
+ module CompilationStages
3
+ module ConvertPartialApplicationsToLambdas
4
+ def convert_partial_applications_to_lambdas(sexp)
5
+ if sexp.instance_of?(Array)
6
+ rator = sexp[0]
7
+ if rator.kind_of?(Symbol)
8
+ rands = sexp[1..-1]
9
+ converted_rands = rands.map do |rand|
10
+ convert_partial_applications_to_lambdas(rand)
11
+ end
12
+
13
+ rator_arity = arity(rator)
14
+ if rator_arity == -1 || rator_arity == rands.length
15
+ converted_rands.unshift(rator)
16
+ elsif rator_arity > rands.length
17
+ # Partial application
18
+ vars = (rator_arity - rands.length).times.map { fresh_variable }
19
+ [:lambda, vars, [rator] + converted_rands + vars]
20
+ else
21
+ # Uncurrying
22
+ now_rands = rands[0...rator_arity]
23
+ deferred_rands = rands[rator_arity..-1]
24
+ [[rator] + now_rands, *deferred_rands]
25
+ end
26
+ else
27
+ sexp.map { |form| convert_partial_applications_to_lambdas(form) }
28
+ end
29
+ else
30
+ sexp
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,147 @@
1
+ module Klam
2
+ module CompilationStages
3
+ # Convert Self Tail Calls To Loops
4
+ #
5
+ # The Shen specification requires that self tail calls be optimized. This
6
+ # stage transforms defuns containing self tail calls into a loop/recur form
7
+ # akin to Clojure's loop/recur. The code emitter knows how to handle these
8
+ # synthetic special forms.
9
+ #
10
+ # For example, this code with tail calls:
11
+ #
12
+ # (defun fact-iter (N Accum)
13
+ # (if (= N 0)
14
+ # Accum
15
+ # (fact-iter (- N 1) (* N Accum))))
16
+ #
17
+ # is converted to:
18
+ #
19
+ # (defun fact-iter (N Accum)
20
+ # ([LOOP]
21
+ # (if (= N 0)
22
+ # Accum
23
+ # ([RECUR] (N Accum) ((- N 1) (* N Accum))))))
24
+ #
25
+ # The variable names are carried along with their new binding expressions
26
+ # in the [RECUR] form so that they can be rebound before looping.
27
+ #
28
+ # Note that this rebinding of variables causes a wrinkle with respect to
29
+ # closures created in the body. Those closures should close over the value
30
+ # at the point of closing rather than the ultimate values after rebinding.
31
+ # To solve this, another sythetic primitive, [FIX-VARS], is used to wrap
32
+ # these cases and the emitted Ruby code samples those variables.
33
+ #
34
+ # This compilation stage must come _after_ SimplifyBooleanOperations and
35
+ # ConvertFreezesToLambdas.
36
+ module ConvertSelfTailCallsToLoops
37
+ def convert_self_tail_calls_to_loops(sexp)
38
+ # Self tail calls can only be found in functions defined by defun.
39
+ # defun is only allowed at the top level, so there's no need to
40
+ # walk the tree if this form is not a defun.
41
+ if sexp.kind_of?(Array) && sexp[0] == :defun
42
+ _, name, params, body = sexp
43
+ if contains_self_tail_calls?(body, name)
44
+ insert_loop_and_recur_into_defun(fix_vars(sexp, params))
45
+ else
46
+ sexp
47
+ end
48
+ else
49
+ sexp
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def contains_self_tail_calls?(body, name)
56
+ if body.instance_of?(Array)
57
+ case body[0]
58
+ when name
59
+ true
60
+ when :if
61
+ _, _, true_expr, false_expr = body
62
+ contains_self_tail_calls?(true_expr, name) ||
63
+ contains_self_tail_calls?(false_expr, name)
64
+ when :let
65
+ contains_self_tail_calls?(body[3], name)
66
+ when :do
67
+ contains_self_tail_calls?(body[2], name)
68
+ else
69
+ false
70
+ end
71
+ else
72
+ false
73
+ end
74
+ end
75
+
76
+ def insert_loop_and_recur_into_defun(form)
77
+ rator, name, params, body = form
78
+ body_with_loop = [:"[LOOP]", name, params,
79
+ insert_recur_into_expr(body, name, params)]
80
+ [rator, name, params, body_with_loop]
81
+ end
82
+
83
+ def insert_recur_into_expr(sexp, name, params)
84
+ if sexp.instance_of?(Array)
85
+ case sexp[0]
86
+ when name
87
+ if sexp.length - 1 == params.length
88
+ [:"[RECUR]", params, sexp[1..-1]]
89
+ else
90
+ sexp
91
+ end
92
+ when :if
93
+ rator, test_expr, true_expr, false_expr = sexp
94
+ [rator,
95
+ test_expr,
96
+ insert_recur_into_expr(true_expr, name, params),
97
+ insert_recur_into_expr(false_expr, name, params)]
98
+ when :let
99
+ rator, var, val_expr, body_expr = sexp
100
+ [rator,
101
+ var,
102
+ val_expr,
103
+ insert_recur_into_expr(body_expr, name, params)]
104
+ when :do
105
+ rator, first_expr, second_expr = sexp
106
+ [rator, first_expr, insert_recur_into_expr(second_expr, name, params)]
107
+ else
108
+ sexp
109
+ end
110
+ else
111
+ sexp
112
+ end
113
+ end
114
+
115
+ def fix_vars(sexp, params)
116
+ if sexp.kind_of?(Array)
117
+ if sexp[0] == :lambda
118
+ referenced_vars = vars_referenced_in(sexp, params)
119
+ if referenced_vars.empty?
120
+ sexp
121
+ else
122
+ [:"[FIX-VARS]", referenced_vars, sexp]
123
+ end
124
+ else
125
+ # Local variables must also be fixed
126
+ if sexp[0] == :let
127
+ params = params + [sexp[1]]
128
+ end
129
+ sexp.map { |x| fix_vars(x, params) }
130
+ end
131
+ else
132
+ sexp
133
+ end
134
+ end
135
+
136
+ def vars_referenced_in(sexp, vars)
137
+ if sexp.kind_of?(Array)
138
+ sexp.map { |x| vars_referenced_in(x, vars) }.flatten.uniq
139
+ elsif vars.include?(sexp)
140
+ [sexp]
141
+ else
142
+ []
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end