gemite 0.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.
@@ -0,0 +1,435 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ast"
4
+ require_relative "environment"
5
+ require_relative "function"
6
+ require_relative "database"
7
+ require_relative "errors"
8
+
9
+ module Gemite
10
+ # テンプレートノード木(TemplateParserの出力)と文AST(Parserの出力)を評価する
11
+ # ツリーウォーク型インタプリタ。
12
+ #
13
+ # 1リクエスト = 1つのInterpreterインスタンム。 render を1回呼ぶと、その中で
14
+ # ファイル全体を通してグローバル環境(仕様25: グローバル変数はファイル単位)を共有する。
15
+ class Interpreter
16
+ include AST
17
+
18
+ def initialize(get_params: {}, post_params: {}, base_dir: Dir.pwd)
19
+ @base_dir = base_dir
20
+ @global_env = Environment.new
21
+ @global_env.set_local("get", to_gemite_hash(get_params))
22
+ @global_env.set_local("post", to_gemite_hash(post_params))
23
+ @open_databases = []
24
+ end
25
+
26
+ # テンプレートノード列を実行し、出力HTML文字列を返す。
27
+ # redirect() が呼ばれた場合は Gemite::RedirectSignal が、
28
+ # 未捕捉のエラーが発生した場合は Gemite::RuntimeError 等がそのまま送出される
29
+ # (呼び出し元のHTTPサーバ層で捕捉して適切なレスポンスに変換する)。
30
+ def render(template_nodes)
31
+ @output = +""
32
+ exec_tpl_nodes(template_nodes, @global_env)
33
+ @output
34
+ ensure
35
+ close_databases
36
+ end
37
+
38
+ private
39
+
40
+ def close_databases
41
+ @open_databases.each { |db| db.close rescue nil }
42
+ end
43
+
44
+ def to_gemite_hash(h)
45
+ result = {}
46
+ (h || {}).each { |k, v| result[k.to_s.to_sym] = v }
47
+ result
48
+ end
49
+
50
+ # ============ テンプレートノードの実行 ============
51
+
52
+ def exec_tpl_nodes(nodes, env)
53
+ nodes.each { |n| exec_tpl_node(n, env) }
54
+ end
55
+
56
+ def exec_tpl_node(node, env)
57
+ case node
58
+ when TplText
59
+ @output << node.text
60
+ when TplExpr
61
+ @output << stringify(eval_expr(node.expr, env))
62
+ when TplCode
63
+ exec_statements(node.statements, env)
64
+ when TplIf
65
+ exec_tpl_if(node, env)
66
+ when TplFor
67
+ exec_tpl_for(node, env)
68
+ else
69
+ raise "internal error: unknown template node #{node.class}"
70
+ end
71
+ end
72
+
73
+ def exec_tpl_if(node, env)
74
+ branch = node.branches.find { |cond, _body| truthy?(eval_expr(cond, env)) }
75
+ if branch
76
+ exec_tpl_nodes(branch[1], env)
77
+ elsif node.else_body
78
+ exec_tpl_nodes(node.else_body, env)
79
+ end
80
+ end
81
+
82
+ def exec_tpl_for(node, env)
83
+ items = to_enumerable(eval_expr(node.iterable, env))
84
+ with_loop_var(env, node.var_name, items) do |item|
85
+ env.set_local(node.var_name, item)
86
+ exec_tpl_nodes(node.body, env)
87
+ end
88
+ end
89
+
90
+ # ============ 文の実行 ============
91
+ # 戻り値は「最後に実行した文の値」(関数の暗黙の戻り値に使う)。
92
+
93
+ def exec_statements(stmts, env)
94
+ result = nil
95
+ stmts.each { |s| result = exec_statement(s, env) }
96
+ result
97
+ end
98
+
99
+ def exec_statement(stmt, env)
100
+ case stmt
101
+ when Assignment
102
+ val = eval_expr(stmt.value, env)
103
+ env.assign(stmt.name, val)
104
+ val
105
+ when FunctionDef
106
+ env.assign(stmt.name, Function.new(stmt.name, stmt.params, stmt.body, env))
107
+ nil
108
+ when ReturnStmt
109
+ raise Gemite::ReturnSignal, eval_expr(stmt.value, env)
110
+ when ExprStmt
111
+ eval_expr(stmt.expr, env)
112
+ when IfStmt
113
+ exec_if_stmt(stmt, env)
114
+ when ForStmt
115
+ exec_for_stmt(stmt, env)
116
+ nil
117
+ when BeginCatchStmt
118
+ exec_begin_catch(stmt, env)
119
+ else
120
+ raise "internal error: unknown statement #{stmt.class}"
121
+ end
122
+ end
123
+
124
+ def exec_if_stmt(stmt, env)
125
+ branch = stmt.branches.find { |cond, _body| truthy?(eval_expr(cond, env)) }
126
+ if branch
127
+ exec_statements(branch[1], env)
128
+ elsif stmt.else_body
129
+ exec_statements(stmt.else_body, env)
130
+ end
131
+ end
132
+
133
+ def exec_for_stmt(stmt, env)
134
+ items = to_enumerable(eval_expr(stmt.iterable, env))
135
+ with_loop_var(env, stmt.var_name, items) do |item|
136
+ env.set_local(stmt.var_name, item)
137
+ exec_statements(stmt.body, env)
138
+ end
139
+ end
140
+
141
+ # @forのループ変数だけを退避/復元するヘルパー(仕様25: @for変数はループ内のみ有効)。
142
+ # ループ変数以外への代入は通常通り現在のスコープに作用する。
143
+ def with_loop_var(env, var_name, items)
144
+ had_prior = env.has_local?(var_name)
145
+ prior_val = env.get_local(var_name)
146
+ items.each { |item| yield item }
147
+ ensure
148
+ if had_prior
149
+ env.set_local(var_name, prior_val)
150
+ else
151
+ env.delete_local(var_name)
152
+ end
153
+ end
154
+
155
+ def exec_begin_catch(stmt, env)
156
+ exec_statements(stmt.begin_body, env)
157
+ rescue Gemite::ReturnSignal, Gemite::RedirectSignal
158
+ raise
159
+ rescue StandardError => e
160
+ env.assign(stmt.err_name, e.message)
161
+ exec_statements(stmt.catch_body, env)
162
+ end
163
+
164
+ def to_enumerable(val)
165
+ case val
166
+ when Array then val
167
+ when nil then []
168
+ else
169
+ raise Gemite::RuntimeError, "@for でループできない値です: #{type_name(val)}"
170
+ end
171
+ end
172
+
173
+ # ============ 式の評価 ============
174
+
175
+ def eval_expr(expr, env)
176
+ case expr
177
+ when IntLiteral, FloatLiteral, BoolLiteral, NilLiteral
178
+ expr.value
179
+ when StringLiteral
180
+ eval_string_literal(expr, env)
181
+ when ArrayLiteral
182
+ expr.elements.map { |e| eval_expr(e, env) }
183
+ when HashLiteral
184
+ h = {}
185
+ expr.pairs.each { |k, v| h[k] = eval_expr(v, env) }
186
+ h
187
+ when Identifier
188
+ env.get(expr.name)
189
+ when BinaryOp
190
+ eval_binary_op(expr, env)
191
+ when UnaryOp
192
+ eval_unary_op(expr, env)
193
+ when LogicalOp
194
+ eval_logical_op(expr, env)
195
+ when MemberAccess
196
+ member_access(eval_expr(expr.receiver, env), expr.name)
197
+ when IndexAccess
198
+ index_access(eval_expr(expr.receiver, env), eval_expr(expr.index, env))
199
+ when CallExpr
200
+ eval_call(expr, env)
201
+ when MethodCallExpr
202
+ eval_method_call(expr, env)
203
+ else
204
+ raise "internal error: unknown expression #{expr.class}"
205
+ end
206
+ end
207
+
208
+ def eval_string_literal(expr, env)
209
+ expr.parts.map do |(kind, val)|
210
+ kind == :str ? val : stringify(eval_expr(val, env))
211
+ end.join
212
+ end
213
+
214
+ def eval_binary_op(expr, env)
215
+ left = eval_expr(expr.left, env)
216
+ right = eval_expr(expr.right, env)
217
+ op = expr.op
218
+ case op
219
+ when :+
220
+ eval_plus(left, right)
221
+ when :-, :*, :/, :%
222
+ eval_arith(op, left, right)
223
+ when :==
224
+ values_equal?(left, right)
225
+ when :!=
226
+ !values_equal?(left, right)
227
+ when :<, :<=, :>, :>=
228
+ eval_compare(op, left, right)
229
+ else
230
+ raise "internal error: unknown operator #{op}"
231
+ end
232
+ end
233
+
234
+ def eval_plus(left, right)
235
+ return left + right if left.is_a?(String) && right.is_a?(String)
236
+ return left + right if numeric?(left) && numeric?(right)
237
+
238
+ raise Gemite::RuntimeError, "'+' は #{type_name(left)} と #{type_name(right)} には使えません"
239
+ end
240
+
241
+ def eval_arith(op, left, right)
242
+ unless numeric?(left) && numeric?(right)
243
+ raise Gemite::RuntimeError, "'#{op}' 演算子は数値同士にのみ使えます (#{type_name(left)}, #{type_name(right)})"
244
+ end
245
+
246
+ left.public_send(op, right)
247
+ rescue ZeroDivisionError
248
+ raise Gemite::RuntimeError, "ゼロ除算です"
249
+ end
250
+
251
+ def eval_compare(op, left, right)
252
+ comparable = (numeric?(left) && numeric?(right)) || (left.is_a?(String) && right.is_a?(String))
253
+ raise Gemite::RuntimeError, "'#{op}' で #{type_name(left)} と #{type_name(right)} を比較できません" unless comparable
254
+
255
+ left.public_send(op, right)
256
+ end
257
+
258
+ def eval_unary_op(expr, env)
259
+ val = eval_expr(expr.operand, env)
260
+ case expr.op
261
+ when :-@
262
+ raise Gemite::RuntimeError, "単項 '-' は数値にのみ使えます (#{type_name(val)})" unless numeric?(val)
263
+
264
+ -val
265
+ when :not
266
+ !truthy?(val)
267
+ else
268
+ raise "internal error: unknown unary operator #{expr.op}"
269
+ end
270
+ end
271
+
272
+ def eval_logical_op(expr, env)
273
+ left = eval_expr(expr.left, env)
274
+ case expr.op
275
+ when :and
276
+ truthy?(left) ? eval_expr(expr.right, env) : left
277
+ when :or
278
+ truthy?(left) ? left : eval_expr(expr.right, env)
279
+ else
280
+ raise "internal error: unknown logical operator #{expr.op}"
281
+ end
282
+ end
283
+
284
+ def member_access(receiver, name)
285
+ return receiver[name.to_sym] if receiver.is_a?(Hash)
286
+
287
+ nil
288
+ end
289
+
290
+ def index_access(receiver, index)
291
+ case receiver
292
+ when Array
293
+ index.is_a?(Integer) ? receiver[index] : nil
294
+ when Hash
295
+ key = index.is_a?(String) ? index.to_sym : index
296
+ receiver[key]
297
+ else
298
+ nil
299
+ end
300
+ end
301
+
302
+ BUILTIN_FUNCTIONS = %w[sqlite redirect puts].freeze
303
+
304
+ def eval_call(expr, env)
305
+ args = expr.args.map { |a| eval_expr(a, env) }
306
+ if BUILTIN_FUNCTIONS.include?(expr.callee_name)
307
+ call_builtin(expr.callee_name, args)
308
+ else
309
+ fn = env.get(expr.callee_name)
310
+ raise Gemite::RuntimeError, "未定義の関数です: #{expr.callee_name}" unless fn.is_a?(Gemite::Function)
311
+
312
+ call_function(fn, args)
313
+ end
314
+ end
315
+
316
+ def call_builtin(name, args)
317
+ case name
318
+ when "sqlite"
319
+ path = args[0]
320
+ raise Gemite::RuntimeError, "sqlite() にはファイルパス(文字列)を渡してください" unless path.is_a?(String)
321
+
322
+ db = Gemite::Database.new(resolve_db_path(path))
323
+ @open_databases << db
324
+ db
325
+ when "redirect"
326
+ raise Gemite::RedirectSignal, args[0].to_s
327
+ when "puts"
328
+ $stdout.puts(args.map { |a| stringify(a) }.join(" "))
329
+ $stdout.flush
330
+ nil
331
+ end
332
+ end
333
+
334
+ def resolve_db_path(path)
335
+ return path if path.start_with?("/")
336
+
337
+ File.join(@base_dir, path)
338
+ end
339
+
340
+ def call_function(fn, args)
341
+ call_env = Environment.new(parent: fn.closure_env, boundary: true)
342
+ fn.params.each_with_index { |param, i| call_env.set_local(param, args[i]) }
343
+ exec_statements(fn.body, call_env)
344
+ rescue Gemite::ReturnSignal => e
345
+ e.value
346
+ end
347
+
348
+ def eval_method_call(expr, env)
349
+ receiver = eval_expr(expr.receiver, env)
350
+ args = expr.args.map { |a| eval_expr(a, env) }
351
+ case receiver
352
+ when Gemite::Database
353
+ call_database_method(receiver, expr.method_name, args)
354
+ else
355
+ raise Gemite::RuntimeError, "#{type_name(receiver)} に対して '#{expr.method_name}' は呼び出せません"
356
+ end
357
+ end
358
+
359
+ def call_database_method(db, name, args)
360
+ case name
361
+ when "select"
362
+ db.select(args[0], args[1] || {})
363
+ when "insert"
364
+ db.insert(args[0], args[1] || {})
365
+ when "update"
366
+ db.update(args[0], args[1] || {}, args[2] || {})
367
+ when "delete"
368
+ db.delete(args[0], args[1] || {})
369
+ when "exec"
370
+ db.exec(args[0])
371
+ else
372
+ raise Gemite::RuntimeError, "データベースに '#{name}' メソッドはありません"
373
+ end
374
+ end
375
+
376
+ # ============ 値のユーティリティ ============
377
+
378
+ def numeric?(v)
379
+ v.is_a?(Integer) || v.is_a?(Float)
380
+ end
381
+
382
+ def values_equal?(a, b)
383
+ a == b
384
+ rescue StandardError
385
+ false
386
+ end
387
+
388
+ # 仕様24: false / nil / 0 / "" / [] / {} が偽、それ以外は真。
389
+ def truthy?(val)
390
+ return false if val.nil? || val == false
391
+ return false if val.is_a?(Numeric) && val.zero?
392
+ return false if val.is_a?(String) && val.empty?
393
+ return false if val.is_a?(Array) && val.empty?
394
+ return false if val.is_a?(Hash) && val.empty?
395
+
396
+ true
397
+ end
398
+
399
+ def type_name(v)
400
+ case v
401
+ when nil then "nil"
402
+ when true, false then "真偽値"
403
+ when Integer, Float then "数値"
404
+ when String then "文字列"
405
+ when Array then "配列"
406
+ when Hash then "連想配列"
407
+ when Gemite::Function then "関数"
408
+ when Gemite::Database then "データベース"
409
+ else v.class.name
410
+ end
411
+ end
412
+
413
+ def stringify(val)
414
+ case val
415
+ when nil then ""
416
+ when String then val
417
+ when Integer, Float then val.to_s
418
+ when true then "true"
419
+ when false then "false"
420
+ when Array
421
+ "[" + val.map { |v| stringify_debug(v) }.join(", ") + "]"
422
+ when Hash
423
+ "{" + val.map { |k, v| "#{k}: #{stringify_debug(v)}" }.join(", ") + "}"
424
+ when Gemite::Function, Gemite::Database
425
+ val.to_s
426
+ else
427
+ val.to_s
428
+ end
429
+ end
430
+
431
+ def stringify_debug(val)
432
+ val.is_a?(String) ? val.inspect : stringify(val)
433
+ end
434
+ end
435
+ end
@@ -0,0 +1,289 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "token"
4
+ require_relative "errors"
5
+
6
+ module Gemite
7
+ # Gemiteの「コード」部分(<?gemite ... ?> の中身や、{ ... } 補間の中身)を
8
+ # トークン列に変換する。HTMLの生テキストは扱わない(それは TemplateParser の仕事)。
9
+ class Lexer
10
+ KEYWORDS = {
11
+ "def" => :DEF,
12
+ "end" => :END,
13
+ "return" => :RETURN,
14
+ "true" => :TRUE,
15
+ "false" => :FALSE,
16
+ "nil" => :NIL,
17
+ "and" => :AND,
18
+ "or" => :OR,
19
+ "not" => :NOT,
20
+ "begin" => :BEGIN,
21
+ "catch" => :CATCH,
22
+ "in" => :IN
23
+ }.freeze
24
+
25
+ # @if / @for などの "@"付きディレクティブ。コードブロック内でも仕様通り
26
+ # @endif / @end を使う(defやbeginの素のendとは別体系)。
27
+ AT_KEYWORDS = {
28
+ "if" => :AT_IF,
29
+ "elseif" => :AT_ELSEIF,
30
+ "else" => :AT_ELSE,
31
+ "endif" => :AT_ENDIF,
32
+ "for" => :AT_FOR,
33
+ "end" => :AT_END
34
+ }.freeze
35
+
36
+ OPERATORS = [
37
+ ["==", :EQ], ["!=", :NEQ], ["<=", :LTE], [">=", :GTE],
38
+ ["<", :LT], [">", :GT], ["=", :ASSIGN],
39
+ ["+", :PLUS], ["-", :MINUS], ["*", :STAR], ["/", :SLASH], ["%", :PERCENT],
40
+ ["(", :LPAREN], [")", :RPAREN], ["[", :LBRACKET], ["]", :RBRACKET],
41
+ ["{", :LBRACE], ["}", :RBRACE], [",", :COMMA], [":", :COLON], [".", :DOT]
42
+ ].freeze
43
+
44
+ def initialize(source, line: 1)
45
+ @src = source
46
+ @pos = 0
47
+ @len = source.length
48
+ @line = line
49
+ end
50
+
51
+ # 直前のトークンがこれらのいずれかである場合、続く改行は「行継続」とみなし
52
+ # NEWLINEトークンを生成しない。これにより
53
+ # comments =
54
+ # db.select(...)
55
+ # のように "=" や演算子・開き括弧・カンマの直後で改行して値を次行に続える
56
+ # 書き方(仕様書28の例で実際に使われている)を、文終端の改行と区別できる。
57
+ CONTINUATION_TOKENS = %i[
58
+ ASSIGN PLUS MINUS STAR SLASH PERCENT
59
+ EQ NEQ LT LTE GT GTE AND OR NOT
60
+ COMMA COLON DOT LPAREN LBRACKET LBRACE
61
+ ].freeze
62
+
63
+ def tokenize
64
+ tokens = []
65
+ loop do
66
+ tok = next_token
67
+ if tok.type == :NEWLINE && CONTINUATION_TOKENS.include?(tokens.last&.type)
68
+ next
69
+ end
70
+ tokens << tok
71
+ break if tok.type == :EOF
72
+ end
73
+ tokens
74
+ end
75
+
76
+ private
77
+
78
+ def peek_char(offset = 0)
79
+ idx = @pos + offset
80
+ idx < @len ? @src[idx] : nil
81
+ end
82
+
83
+ def advance_char
84
+ ch = @src[@pos]
85
+ @pos += 1
86
+ @line += 1 if ch == "\n"
87
+ ch
88
+ end
89
+
90
+ def eof?
91
+ @pos >= @len
92
+ end
93
+
94
+ def next_token
95
+ skip_insignificant
96
+ start_line = @line
97
+ return Token.new(:EOF, nil, start_line) if eof?
98
+
99
+ ch = peek_char
100
+ return lex_newline(start_line) if ch == "\n"
101
+ return lex_number(start_line) if digit?(ch)
102
+ return lex_string(start_line) if ch == '"'
103
+ return lex_at_keyword(start_line) if ch == "@"
104
+ return lex_ident(start_line) if ident_start?(ch)
105
+
106
+ lex_operator(start_line)
107
+ end
108
+
109
+ def digit?(ch)
110
+ !ch.nil? && ch >= "0" && ch <= "9"
111
+ end
112
+
113
+ def ident_start?(ch)
114
+ !ch.nil? && (ch =~ /[A-Za-z_]/)
115
+ end
116
+
117
+ def ident_char?(ch)
118
+ !ch.nil? && (ch =~ /[A-Za-z0-9_]/)
119
+ end
120
+
121
+ # 改行以外の空白・コメント(# から行末まで)を読み飛ばす。
122
+ # 改行はステートメント区切りとして意味を持つのでここでは消費しない。
123
+ def skip_insignificant
124
+ loop do
125
+ ch = peek_char
126
+ if ch == " " || ch == "\t" || ch == "\r"
127
+ advance_char
128
+ elsif ch == "#"
129
+ advance_char while peek_char && peek_char != "\n"
130
+ else
131
+ break
132
+ end
133
+ end
134
+ end
135
+
136
+ # 連続する改行(間の空白/コメント含む)をまとめて1つのNEWLINEトークンにする
137
+ def lex_newline(line)
138
+ loop do
139
+ advance_char if peek_char == "\n"
140
+ skip_insignificant
141
+ break unless peek_char == "\n"
142
+ end
143
+ Token.new(:NEWLINE, nil, line)
144
+ end
145
+
146
+ def lex_number(line)
147
+ start = @pos
148
+ advance_char while digit?(peek_char)
149
+ if peek_char == "." && digit?(peek_char(1))
150
+ advance_char
151
+ advance_char while digit?(peek_char)
152
+ return Token.new(:FLOAT, @src[start...@pos].to_f, line)
153
+ end
154
+ Token.new(:INT, @src[start...@pos].to_i, line)
155
+ end
156
+
157
+ def lex_ident(line)
158
+ start = @pos
159
+ advance_char while ident_char?(peek_char)
160
+ word = @src[start...@pos]
161
+ type = KEYWORDS[word]
162
+ type ? Token.new(type, word, line) : Token.new(:IDENT, word, line)
163
+ end
164
+
165
+ def lex_at_keyword(line)
166
+ advance_char # '@'
167
+ start = @pos
168
+ advance_char while ident_char?(peek_char)
169
+ word = @src[start...@pos]
170
+ type = AT_KEYWORDS[word]
171
+ raise Gemite::SyntaxError.new("不明なディレクティブ '@#{word}'", line: line) unless type
172
+
173
+ Token.new(type, "@#{word}", line)
174
+ end
175
+
176
+ # 文字列リテラル。#{ ... } による式補間を [:str, text] / [:expr, source] の
177
+ # 配列として保持する(実際の式へのパースは Parser 側で行う)。
178
+ def lex_string(line)
179
+ advance_char # 開き "
180
+ parts = []
181
+ buf = +""
182
+ loop do
183
+ ch = peek_char
184
+ raise Gemite::SyntaxError.new("文字列リテラルが閉じられていません", line: line) if ch.nil?
185
+
186
+ case ch
187
+ when '"'
188
+ advance_char
189
+ break
190
+ when "\\"
191
+ advance_char
192
+ buf << unescape(peek_char)
193
+ advance_char
194
+ when "#"
195
+ if peek_char(1) == "{"
196
+ parts << [:str, buf] unless buf.empty?
197
+ buf = +""
198
+ advance_char # '#'
199
+ advance_char # '{'
200
+ parts << [:expr, scan_interpolation_source(line)]
201
+ else
202
+ buf << ch
203
+ advance_char
204
+ end
205
+ else
206
+ buf << ch
207
+ advance_char
208
+ end
209
+ end
210
+ parts << [:str, buf] unless buf.empty?
211
+ parts << [:str, ""] if parts.empty?
212
+ Token.new(:STRING, parts, line)
213
+ end
214
+
215
+ def unescape(ch)
216
+ case ch
217
+ when "n" then "\n"
218
+ when "t" then "\t"
219
+ when "r" then "\r"
220
+ when '"' then '"'
221
+ when "\\" then "\\"
222
+ when "#" then "#"
223
+ when nil then ""
224
+ else ch
225
+ end
226
+ end
227
+
228
+ # #{ の直後から対応する } までの生ソースを取り出す。
229
+ # ネストした {}(ハッシュリテラル等)や、内部の文字列リテラルにも対応する。
230
+ def scan_interpolation_source(line)
231
+ depth = 1
232
+ in_str = false
233
+ buf = +""
234
+ loop do
235
+ ch = peek_char
236
+ raise Gemite::SyntaxError.new("補間式 #{'#'}{...} が閉じられていません", line: line) if ch.nil?
237
+
238
+ if in_str
239
+ buf << ch
240
+ if ch == "\\"
241
+ advance_char
242
+ buf << peek_char.to_s
243
+ advance_char
244
+ elsif ch == '"'
245
+ in_str = false
246
+ advance_char
247
+ else
248
+ advance_char
249
+ end
250
+ next
251
+ end
252
+
253
+ case ch
254
+ when '"'
255
+ in_str = true
256
+ buf << ch
257
+ advance_char
258
+ when "{"
259
+ depth += 1
260
+ buf << ch
261
+ advance_char
262
+ when "}"
263
+ depth -= 1
264
+ if depth.zero?
265
+ advance_char
266
+ break
267
+ else
268
+ buf << ch
269
+ advance_char
270
+ end
271
+ else
272
+ buf << ch
273
+ advance_char
274
+ end
275
+ end
276
+ buf
277
+ end
278
+
279
+ def lex_operator(line)
280
+ OPERATORS.each do |(text, type)|
281
+ if @src[@pos, text.length] == text
282
+ text.length.times { advance_char }
283
+ return Token.new(type, text, line)
284
+ end
285
+ end
286
+ raise Gemite::SyntaxError.new("不明な文字 '#{peek_char}'", line: line)
287
+ end
288
+ end
289
+ end