Class: Gemite::TemplateParser
- Inherits:
-
Object
- Object
- Gemite::TemplateParser
- Includes:
- AST
- Defined in:
- lib/gemite/template_parser.rb
Overview
.tz ファイルの生テキストを走査し、以下のノード列(木)を組み立てる:
TplText - そのまま出力する生HTML
TplExpr - { expr } 補間。式の評価結果を文字列化して出力する
TplCode - <?gemite ... ?> ブロック。中身は文として実行するだけで出力はしない
TplIf - @if/@elseif/@else/@endif (HTML側)。分岐に応じて子ノードを出力する
TplFor - @for x in xs ... @end (HTML側)。反復して子ノードを出力する
の中身、および @if/@for がコードブロック内で使われた場合は、
Parser(文パーサー)側に委譲する。@if/@endif, @for/@end はテンプレート層・ コード層の両方に現れうるが、defやbeginの素のendはコード層にしか現れない。
Constant Summary collapse
- CODE_TAG_OPEN =
コードブロックの開始タグ。文字数に依存する処理はすべてこの定数の .length から動的に算出し、名称変更があってもズレないようにする。
"<?gemite"- CODE_TAG_CLOSE =
"?>"- DIRECTIVE_WITH_CLAUSE =
@if/@elseif/@for は条件式・イテラブル式(行の残り)を伴う。 @else/@endif/@end は節を取らないので、キーワード自体だけを消費する (でないと同じ行に続くHTMLやを誤って飲み込んでしまう)。 行頭(前後の空白のみ許容)に限定することで、HTML本文中の"@"(メールアドレス等)との衝突も避ける。
/\A[ \t]*@(if|elseif|for)\b[ \t]*(.*)\z/- DIRECTIVE_BARE =
/\A[ \t]*@(else|endif|end)\b[ \t]*/
Class Method Summary collapse
Instance Method Summary collapse
-
#initialize(source) ⇒ TemplateParser
constructor
A new instance of TemplateParser.
-
#parse_nodes(stop_directives) ⇒ Object
stop_directives: nilならEOFまで。配列ならそのディレクティブ名(文字列)に 出会うまで(消費はしない。呼び出し元がexpectする)。.
Constructor Details
#initialize(source) ⇒ TemplateParser
Returns a new instance of TemplateParser.
37 38 39 40 41 42 |
# File 'lib/gemite/template_parser.rb', line 37 def initialize(source) @src = source @pos = 0 @len = source.length @line = 1 end |
Class Method Details
.parse(source) ⇒ Object
33 34 35 |
# File 'lib/gemite/template_parser.rb', line 33 def self.parse(source) new(source).parse_nodes(nil) end |
Instance Method Details
#parse_nodes(stop_directives) ⇒ Object
stop_directives: nilならEOFまで。配列ならそのディレクティブ名(文字列)に 出会うまで(消費はしない。呼び出し元がexpectする)。
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/gemite/template_parser.rb', line 46 def parse_nodes(stop_directives) nodes = [] text_buf = +"" text_start_line = @line flush = lambda do unless text_buf.empty? nodes << TplText.new(text_buf) text_buf = +"" end end until eof? # <?gemite ... ?> コードブロック if looking_at?(CODE_TAG_OPEN) flush.call nodes << parse_code_block text_start_line = @line next end # { expr } 補間 (失敗したら生テキストとしてフォールバック) if peek_char == "{" expr_node = try_parse_interpolation if expr_node flush.call nodes << expr_node text_start_line = @line next end end # 行頭ディレクティブ (@if/@elseif/@else/@endif/@for/@end) if at_line_start? directive, clause, consumed_len = peek_directive_at_current_line if directive if stop_directives && stop_directives.include?(directive) # ここでは消費しない。呼び出し元が consume_matched_directive で読み進める。 flush.call return nodes end consume_chars(consumed_len) case directive when "if" flush.call nodes << parse_template_if(clause) text_start_line = @line next when "for" flush.call nodes << parse_template_for(clause) text_start_line = @line next when "elseif", "else", "endif", "end" # 対応する開始ディレクティブなしに出現した閉じ/中間ディレクティブ raise Gemite::SyntaxError.new("対応する開始タグのない '@#{directive}' です", line: @line) end end end ch = advance_char text_buf << ch end if stop_directives raise Gemite::SyntaxError.new( "'#{stop_directives.map { |d| "@#{d}" }.join("/")}' が見つからないまま終端しました", line: text_start_line ) end flush.call nodes end |