sfrp 1.1.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.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/.ctags +3 -0
  3. data/.editorconfig +9 -0
  4. data/.gitignore +14 -0
  5. data/.rubocop.yml +629 -0
  6. data/.travis.yml +12 -0
  7. data/Gemfile +2 -0
  8. data/LICENSE +28 -0
  9. data/README.md +34 -0
  10. data/Rakefile +1 -0
  11. data/base-library/Base.sfrp +81 -0
  12. data/base-library/IO/AVR/ATMEGA8.c +9 -0
  13. data/base-library/IO/AVR/ATMEGA8.h +6 -0
  14. data/base-library/IO/AVR/ATMEGA8.sfrp +4 -0
  15. data/base-library/IO/STDIO.c +40 -0
  16. data/base-library/IO/STDIO.h +13 -0
  17. data/base-library/IO/STDIO.sfrp +10 -0
  18. data/bin/sfrp +7 -0
  19. data/lib/sfrp.rb +2 -0
  20. data/lib/sfrp/command.rb +73 -0
  21. data/lib/sfrp/compiler.rb +94 -0
  22. data/lib/sfrp/error.rb +4 -0
  23. data/lib/sfrp/file.rb +18 -0
  24. data/lib/sfrp/flat/dsl.rb +33 -0
  25. data/lib/sfrp/flat/elements.rb +90 -0
  26. data/lib/sfrp/flat/exception.rb +45 -0
  27. data/lib/sfrp/flat/expression.rb +125 -0
  28. data/lib/sfrp/flat/set.rb +61 -0
  29. data/lib/sfrp/input/exception.rb +16 -0
  30. data/lib/sfrp/input/parser.rb +417 -0
  31. data/lib/sfrp/input/set.rb +29 -0
  32. data/lib/sfrp/input/transformer.rb +219 -0
  33. data/lib/sfrp/low/dsl.rb +126 -0
  34. data/lib/sfrp/low/element.rb +126 -0
  35. data/lib/sfrp/low/set.rb +62 -0
  36. data/lib/sfrp/mono/dsl.rb +120 -0
  37. data/lib/sfrp/mono/environment.rb +26 -0
  38. data/lib/sfrp/mono/exception.rb +21 -0
  39. data/lib/sfrp/mono/expression.rb +124 -0
  40. data/lib/sfrp/mono/function.rb +86 -0
  41. data/lib/sfrp/mono/memory.rb +32 -0
  42. data/lib/sfrp/mono/node.rb +125 -0
  43. data/lib/sfrp/mono/pattern.rb +69 -0
  44. data/lib/sfrp/mono/set.rb +151 -0
  45. data/lib/sfrp/mono/type.rb +210 -0
  46. data/lib/sfrp/mono/vconst.rb +134 -0
  47. data/lib/sfrp/output/set.rb +33 -0
  48. data/lib/sfrp/poly/dsl.rb +171 -0
  49. data/lib/sfrp/poly/elements.rb +168 -0
  50. data/lib/sfrp/poly/exception.rb +42 -0
  51. data/lib/sfrp/poly/expression.rb +170 -0
  52. data/lib/sfrp/poly/monofier.rb +73 -0
  53. data/lib/sfrp/poly/set.rb +90 -0
  54. data/lib/sfrp/poly/typing.rb +197 -0
  55. data/lib/sfrp/raw/dsl.rb +41 -0
  56. data/lib/sfrp/raw/elements.rb +164 -0
  57. data/lib/sfrp/raw/exception.rb +40 -0
  58. data/lib/sfrp/raw/expression.rb +168 -0
  59. data/lib/sfrp/raw/namespace.rb +30 -0
  60. data/lib/sfrp/raw/set.rb +109 -0
  61. data/lib/sfrp/version.rb +3 -0
  62. data/sfrp.gemspec +40 -0
  63. data/spec/sfrp/Test.sfrp +4 -0
  64. data/spec/sfrp/compiler_spec.rb +17 -0
  65. data/spec/sfrp/flat/set_spec.rb +40 -0
  66. data/spec/sfrp/input/parse_test.sfrp +20 -0
  67. data/spec/sfrp/input/set_spec.rb +18 -0
  68. data/spec/sfrp/low/set_spec.rb +20 -0
  69. data/spec/sfrp/mono/expected.yml +295 -0
  70. data/spec/sfrp/mono/set_spec.rb +152 -0
  71. data/spec/sfrp/output/set_spec.rb +29 -0
  72. data/spec/sfrp/poly/set_spec.rb +290 -0
  73. data/spec/sfrp/raw/set_spec.rb +38 -0
  74. data/spec/spec_helper.rb +16 -0
  75. data/test/IntTest/Main.c +5 -0
  76. data/test/IntTest/Main.h +6 -0
  77. data/test/IntTest/Main.sfrp +10 -0
  78. data/test/IntTest/in.txt +3 -0
  79. data/test/IntTest/out.txt +4 -0
  80. data/test/MaybeTest/Main.sfrp +8 -0
  81. data/test/MaybeTest/SubDir/Lib.sfrp +9 -0
  82. data/test/MaybeTest/in.txt +6 -0
  83. data/test/MaybeTest/out.txt +6 -0
  84. data/test/Rakefile +15 -0
  85. metadata +290 -0
@@ -0,0 +1,42 @@
1
+ require 'sfrp/error'
2
+
3
+ module SFRP
4
+ module Poly
5
+ class UndeterminableTypeError < CompileError
6
+ def initialize(identifier, typing)
7
+ @identifier = identifier
8
+ @typing = typing
9
+ end
10
+
11
+ def message
12
+ "undeterminable type #{@typing}"
13
+ end
14
+ end
15
+
16
+ class UnifyError < CompileError
17
+ def initialize(typing1, typing2)
18
+ @typing1 = typing1
19
+ @typing2 = typing2
20
+ end
21
+
22
+ def message
23
+ vars = @typing1.variables + @typing2.variables
24
+ "cannot unify #{@typing1.to_s(vars)} and #{@typing2.to_s(vars)}"
25
+ end
26
+ end
27
+
28
+ class RecursiveError < CompileError
29
+ def initialize(node_strs)
30
+ @node_strs = node_strs
31
+ end
32
+
33
+ def chain_str
34
+ [*@node_strs, @node_strs[0]].join(' -> ')
35
+ end
36
+
37
+ def message
38
+ "recursive node/function path: #{chain_str}"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,170 @@
1
+ module SFRP
2
+ module Poly
3
+ class MatchExp
4
+ Case = Struct.new(:pattern, :exp)
5
+
6
+ def initialize(left_exp, cases, id = nil)
7
+ @left_exp = left_exp
8
+ @cases = cases
9
+ @id = id
10
+ end
11
+
12
+ def typing(set, var_env)
13
+ raise if @typing
14
+ left_exp_typing = @left_exp.typing(set, var_env)
15
+ @typing = Typing.new do |t|
16
+ @cases.each do |c|
17
+ new_var_env = var_env.dup
18
+ left_exp_typing.unify(c.pattern.typing(set, new_var_env))
19
+ t.unify(c.exp.typing(set, new_var_env))
20
+ end
21
+ end
22
+ end
23
+
24
+ def clone
25
+ cloned_cases = @cases.map { |c| Case.new(c.pattern.clone, c.exp.clone) }
26
+ MatchExp.new(@left_exp.clone, cloned_cases, @id)
27
+ end
28
+
29
+ def called_func_strs
30
+ [@left_exp, *@cases.map(&:exp)].flat_map(&:called_func_strs)
31
+ end
32
+
33
+ def to_mono(monofier)
34
+ raise UndeterminableTypeError.new(@id, @typing) unless @typing.mono?
35
+ mono_type_str = monofier.use_type(@typing)
36
+ M.match_e(mono_type_str, @left_exp.to_mono(monofier)) do |m|
37
+ @cases.each do |c|
38
+ m.case(c.pattern.to_mono(monofier)) { c.exp.to_mono(monofier) }
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ class FuncCallExp
45
+ def initialize(func_str, arg_exps, id = nil)
46
+ @func_str = func_str
47
+ @arg_exps = arg_exps
48
+ @id = id
49
+ end
50
+
51
+ def typing(set, var_env)
52
+ raise if @typing
53
+ @ftyping = set.func(@func_str).ftyping(set).instance do |ft|
54
+ ft.params.zip(@arg_exps) { |t, e| e.typing(set, var_env).unify(t) }
55
+ end
56
+ @typing = @ftyping.body
57
+ end
58
+
59
+ def clone
60
+ FuncCallExp.new(@func_str, @arg_exps.map(&:clone), @id)
61
+ end
62
+
63
+ def called_func_strs
64
+ [@func_str, *@arg_exps.flat_map(&:called_func_strs)]
65
+ end
66
+
67
+ def to_mono(monofier)
68
+ raise UndeterminableTypeError.new(@id, @typing) unless @typing.mono?
69
+ mono_func_str = monofier.use_func(@func_str, @ftyping)
70
+ args = @arg_exps.map { |e| e.to_mono(monofier) }
71
+ M.call_e(monofier.use_type(@typing), mono_func_str, *args)
72
+ end
73
+ end
74
+
75
+ class VConstCallExp
76
+ def initialize(vconst_str, arg_exps, id = nil)
77
+ @vconst_str = vconst_str
78
+ @arg_exps = arg_exps
79
+ @id = id
80
+ end
81
+
82
+ def typing(set, var_env)
83
+ raise if @typing
84
+ @ftyping = set.vconst(@vconst_str).ftyping.instance do |ft|
85
+ ft.params.zip(@arg_exps) { |t, e| e.typing(set, var_env).unify(t) }
86
+ end
87
+ @typing = @ftyping.body
88
+ end
89
+
90
+ def clone
91
+ VConstCallExp.new(@vconst_str, @arg_exps.map(&:clone), @id)
92
+ end
93
+
94
+ def called_func_strs
95
+ @arg_exps.flat_map(&:called_func_strs)
96
+ end
97
+
98
+ def to_mono(monofier)
99
+ raise UndeterminableTypeError.new(@id, @typing) unless @typing.mono?
100
+ mono_vconst_str = monofier.use_vconst(@vconst_str, @typing)
101
+ args = @arg_exps.map { |e| e.to_mono(monofier) }
102
+ M.vc_call_e(monofier.use_type(@typing), mono_vconst_str, *args)
103
+ end
104
+ end
105
+
106
+ class VarRefExp
107
+ def initialize(var_str, id = nil)
108
+ @var_str = var_str
109
+ @id = id
110
+ end
111
+
112
+ def typing(_set, var_env)
113
+ @typing = var_env[@var_str]
114
+ end
115
+
116
+ def clone
117
+ VarRefExp.new(@var_str, @id)
118
+ end
119
+
120
+ def called_func_strs
121
+ []
122
+ end
123
+
124
+ def to_mono(monofier)
125
+ raise UndeterminableTypeError.new(@id, @typing) unless @typing.mono?
126
+ M.v_e(monofier.use_type(@typing), @var_str)
127
+ end
128
+ end
129
+
130
+ class Pattern
131
+ def initialize(vconst_str, ref_var_str, patterns, id = nil)
132
+ @vconst_str = vconst_str
133
+ @ref_var_str = ref_var_str
134
+ @patterns = patterns
135
+ @id = id
136
+ end
137
+
138
+ def typing(set, var_env)
139
+ raise if @typing
140
+ @typing = Typing.new do |t|
141
+ var_env[@ref_var_str] = t if @ref_var_str
142
+ if @vconst_str
143
+ set.vconst(@vconst_str).ftyping.instance do |ft|
144
+ @patterns.zip(ft.params) do |pat, param_typing|
145
+ pat.typing(set, var_env).unify(param_typing)
146
+ end
147
+ ft.body.unify(t)
148
+ end
149
+ end
150
+ end
151
+ end
152
+
153
+ def clone
154
+ Pattern.new(@vconst_str, @ref_var_str, @patterns.map(&:clone), @id)
155
+ end
156
+
157
+ def to_mono(monofier)
158
+ raise UndeterminableTypeError.new(@id, @typing) unless @typing.mono?
159
+ mono_type_str = monofier.use_type(@typing)
160
+ if @vconst_str
161
+ mono_vconst_str = monofier.use_vconst(@vconst_str, @typing)
162
+ ch = @patterns.map { |pat| pat.to_mono(monofier) }
163
+ M.pref(mono_type_str, mono_vconst_str, @ref_var_str, *ch)
164
+ else
165
+ M.pany(mono_type_str, @ref_var_str)
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,73 @@
1
+ module SFRP
2
+ module Poly
3
+ class Monofier
4
+ def initialize(src_set, dest_set, &block)
5
+ @src_set = src_set
6
+ @dest_set = dest_set
7
+ @type_str = {}
8
+ @func_str = {}
9
+ @vconst_str = {}
10
+ @node_str = {}
11
+ block.call(self) if block
12
+ end
13
+
14
+ def use_type(typing)
15
+ unique_type_str = typing.tconst_str + '/TYPE/' + typing.unique_str
16
+ if @type_str.key?(unique_type_str)
17
+ @type_str[unique_type_str]
18
+ else
19
+ @type_str[unique_type_str] = 'T' + md5(unique_type_str)
20
+ new_tconst = @src_set.tconst(typing.tconst_str).clone
21
+ new_tconst.typing.unify(typing)
22
+ @dest_set << new_tconst.to_mono(self)
23
+ @type_str[unique_type_str]
24
+ end
25
+ end
26
+
27
+ def use_func(func_str, ftyping)
28
+ unique_func_str = func_str + '/FUNC/' + ftyping.unique_str
29
+ if @func_str.key?(unique_func_str)
30
+ @func_str[unique_func_str]
31
+ else
32
+ new_func = @src_set.func(func_str).clone
33
+ mono_func_str = 'F' + md5(unique_func_str)
34
+ new_func.ftyping(@src_set).unify(ftyping)
35
+ @dest_set << new_func.to_mono(self, mono_func_str)
36
+ @func_str[unique_func_str] = mono_func_str
37
+ end
38
+ end
39
+
40
+ def use_vconst(vconst_str, typing)
41
+ unique_vconst_str = vconst_str + '/VCONST/' + typing.unique_str
42
+ if @vconst_str.key?(unique_vconst_str)
43
+ @vconst_str[unique_vconst_str]
44
+ else
45
+ @vconst_str[unique_vconst_str] = 'V' + md5(unique_vconst_str)
46
+ new_vconst = @src_set.vconst(vconst_str).clone
47
+ new_vconst.ftyping.body.unify(typing)
48
+ @dest_set << new_vconst.to_mono(self)
49
+ @vconst_str[unique_vconst_str]
50
+ end
51
+ end
52
+
53
+ def use_node(node_str)
54
+ unique_node_str = node_str + '/NODE/'
55
+ if @node_str.key?(unique_node_str)
56
+ @node_str[unique_node_str]
57
+ else
58
+ @node_str[unique_node_str] = 'N' + md5(unique_node_str)
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def md5(str)
65
+ require 'digest/md5'
66
+ @record ||= {}
67
+ hash_val = Digest::MD5.hexdigest(str).to_i(16).to_s(36)[0, 20]
68
+ raise "MD5: #{@record[hash_val]} and #{str}" if @record.key?(hash_val)
69
+ hash_val
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,90 @@
1
+ require 'sfrp/poly/exception'
2
+ require 'sfrp/poly/typing'
3
+ require 'sfrp/poly/elements'
4
+ require 'sfrp/poly/expression'
5
+ require 'sfrp/poly/monofier'
6
+ require 'sfrp/poly/dsl'
7
+
8
+ module SFRP
9
+ module Poly
10
+ class Set
11
+ def initialize(&block)
12
+ @func_h = {}
13
+ @node_h = {}
14
+ @tconst_h = {}
15
+ @vconst_h = {}
16
+ @output_node_strs = []
17
+ @init_func_strs = []
18
+ block.call(self) if block
19
+ end
20
+
21
+ def to_mono
22
+ Mono::Set.new do |dest_set|
23
+ @func_h.values.each do |f|
24
+ f.check_recursion(self)
25
+ f.ftyping(self)
26
+ end
27
+ @node_h.values.each do |n|
28
+ n.check_recursion(self)
29
+ n.typing(self)
30
+ end
31
+ Monofier.new(self, dest_set) do |m|
32
+ @init_func_strs.each do |func_str|
33
+ mono_func_str = m.use_func(func_str, func(func_str).ftyping(self))
34
+ dest_set.append_init_func_str(mono_func_str)
35
+ end
36
+ @node_h.values.each do |node|
37
+ dest_set << node.to_mono(m)
38
+ end
39
+ @output_node_strs.each do |node_str|
40
+ dest_set.append_output_node_str(m.use_node(node_str))
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ def <<(element)
47
+ case element
48
+ when Function
49
+ @func_h[element.str] = element
50
+ when Node
51
+ @node_h[element.str] = element
52
+ when TConst
53
+ @tconst_h[element.str] = element
54
+ when VConst
55
+ @vconst_h[element.str] = element
56
+ else
57
+ raise
58
+ end
59
+ end
60
+
61
+ def append_output_node_str(node_str)
62
+ @output_node_strs << node_str
63
+ end
64
+
65
+ def append_init_func_str(init_func_str)
66
+ @init_func_strs << init_func_str
67
+ end
68
+
69
+ def func(func_str)
70
+ raise func_str unless @func_h.key?(func_str)
71
+ @func_h[func_str]
72
+ end
73
+
74
+ def node(node_str)
75
+ raise node_str unless @node_h.key?(node_str)
76
+ @node_h[node_str]
77
+ end
78
+
79
+ def tconst(tconst_str)
80
+ raise tconst_str unless @tconst_h.key?(tconst_str )
81
+ @tconst_h[tconst_str ]
82
+ end
83
+
84
+ def vconst(vconst_str)
85
+ raise vconst_str unless @vconst_h.key?(vconst_str)
86
+ @vconst_h[vconst_str]
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,197 @@
1
+ module SFRP
2
+ module Poly
3
+ class Typing
4
+ def initialize(tconst_str = nil, arg_typings = [], &block)
5
+ @tconst_str = tconst_str
6
+ @arg_typings = arg_typings
7
+ @parent = nil
8
+ block.call(self) if block
9
+ end
10
+
11
+ def tconst_str
12
+ root == self ? @tconst_str : root.tconst_str
13
+ end
14
+
15
+ def unify(other)
16
+ return self if same?(other)
17
+ return root.unify(other) unless root == self
18
+ if variable? && other.variable?
19
+ @parent = other
20
+ elsif variable? && !other.variable?
21
+ raise UnifyError.new(self, other) if occur?(other)
22
+ @parent = other
23
+ elsif !variable? && other.variable?
24
+ other.unify(self)
25
+ else
26
+ unless tconst_str == other.tconst_str && argc == other.argc
27
+ raise UnifyError.new(self, other)
28
+ end
29
+ arg_typings.zip(other.arg_typings) { |a, b| a.unify(b) }
30
+ @parent = other
31
+ end
32
+ end
33
+
34
+ def unique_str
35
+ raise unless mono?
36
+ "#{tconst_str}[#{arg_typings.map(&:unique_str).join(', ')}]"
37
+ end
38
+
39
+ def mono?
40
+ !variable? && arg_typings.all?(&:mono?)
41
+ end
42
+
43
+ def variables
44
+ return [self] if variable?
45
+ arg_typings.flat_map(&:variables)
46
+ end
47
+
48
+ def to_type_annot(vars)
49
+ if variable?
50
+ idx = vars.index { |v| v.same?(self) }
51
+ raise unless idx
52
+ TypeAnnotationVar.new('a' + idx.to_s)
53
+ else
54
+ args = arg_typings.map { |t| t.to_type_annot(vars) }
55
+ TypeAnnotationType.new(tconst_str, args)
56
+ end
57
+ end
58
+
59
+ def to_s(vars = nil)
60
+ vars ||= variables
61
+ to_type_annot(vars).to_s
62
+ end
63
+
64
+ protected
65
+
66
+ def root
67
+ @parent ? (@parent = @parent.root) : self
68
+ end
69
+
70
+ def argc
71
+ arg_typings.size
72
+ end
73
+
74
+ def arg_typings
75
+ root == self ? @arg_typings : root.arg_typings
76
+ end
77
+
78
+ def variable?
79
+ tconst_str.nil?
80
+ end
81
+
82
+ def same?(other)
83
+ return true if root == other.root
84
+ return false if variable? || other.variable?
85
+ return false unless tconst_str == other.tconst_str && argc == other.argc
86
+ arg_typings.zip(other.arg_typings).all? { |a, b| a.same?(b) }
87
+ end
88
+
89
+ def occur?(other)
90
+ raise unless variable?
91
+ return true if same?(other)
92
+ arg_typings.any? { |t| occur?(t) }
93
+ end
94
+ end
95
+
96
+ class FuncTyping
97
+ attr_reader :params, :body
98
+
99
+ def initialize(param_size, &block)
100
+ @params = Array.new(param_size) { Typing.new }
101
+ @body = Typing.new
102
+ block.call(self) if block
103
+ end
104
+
105
+ def to_ftype_annot
106
+ vars = @body.variables + @params.flat_map(&:variables)
107
+ args = @params.map { |t| t.to_type_annot(vars) }
108
+ FuncTypeAnnotation.new(@body.to_type_annot(vars), args)
109
+ end
110
+
111
+ def unify(other)
112
+ raise unless @params.size == other.params.size
113
+ @params.zip(other.params) { |a, b| a.unify(b) }
114
+ @body.unify(other.body)
115
+ self
116
+ end
117
+
118
+ def instance(&block)
119
+ instance = to_ftype_annot.to_ftyping
120
+ block.call(instance) if block
121
+ instance
122
+ end
123
+
124
+ def unique_str
125
+ args = @params + [@body]
126
+ "Func#{@params.size}[#{args.map(&:unique_str).join(', ')}]"
127
+ end
128
+
129
+ def mono?
130
+ [@body, *@params].all?(&:mono?)
131
+ end
132
+
133
+ def to_s
134
+ to_ftype_annot.to_s
135
+ end
136
+ end
137
+
138
+ class FuncTypeAnnotation
139
+ def initialize(ret_type_annot, arg_type_annots)
140
+ @ret_type_annot = ret_type_annot
141
+ @arg_type_annots = arg_type_annots
142
+ end
143
+
144
+ def to_ftyping(var_strs = nil)
145
+ var_strs ||= [@ret_type_annot, *@arg_type_annots].flat_map(&:var_strs)
146
+ tbl = Hash[var_strs.uniq.map { |s| [s, Typing.new] }]
147
+ FuncTyping.new(@arg_type_annots.size) do |ft|
148
+ ft.params.zip(@arg_type_annots) { |t, at| t.unify(at.to_typing(tbl)) }
149
+ ft.body.unify(@ret_type_annot.to_typing(tbl))
150
+ end
151
+ end
152
+
153
+ def to_s
154
+ "(#{@arg_type_annots.map(&:to_s).join(', ')}) -> #{@ret_type_annot}"
155
+ end
156
+ end
157
+
158
+ class TypeAnnotationType
159
+ def initialize(tconst_str, arg_type_annots)
160
+ @tconst_str = tconst_str
161
+ @arg_type_annots = arg_type_annots
162
+ end
163
+
164
+ def to_typing(tbl)
165
+ Typing.new(@tconst_str, @arg_type_annots.map { |ta| ta.to_typing(tbl) })
166
+ end
167
+
168
+ def var_strs
169
+ @arg_type_annots.flat_map(&:var_strs)
170
+ end
171
+
172
+ def to_s
173
+ return @tconst_str if @arg_type_annots.empty?
174
+ "#{@tconst_str}[#{@arg_type_annots.map(&:to_s).join(', ')}]"
175
+ end
176
+ end
177
+
178
+ class TypeAnnotationVar
179
+ def initialize(var_str)
180
+ @var_str = var_str
181
+ end
182
+
183
+ def to_typing(tbl)
184
+ raise var_str unless tbl.key?(@var_str)
185
+ tbl[@var_str]
186
+ end
187
+
188
+ def var_strs
189
+ [@var_str]
190
+ end
191
+
192
+ def to_s
193
+ @var_str
194
+ end
195
+ end
196
+ end
197
+ end