From b6c645260fca28d15814acee2a8793abfb169bd4 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 23 Nov 2015 04:15:54 +0900 Subject: [PATCH] Invent Hamlit::StringInterpolation.compile --- lib/hamlit/compiler/script_compiler.rb | 1 + lib/hamlit/string_interpolation.rb | 60 ++++++++++++++++++++++++ test/hamlit/string_interpolation_test.rb | 41 ++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 lib/hamlit/string_interpolation.rb create mode 100644 test/hamlit/string_interpolation_test.rb diff --git a/lib/hamlit/compiler/script_compiler.rb b/lib/hamlit/compiler/script_compiler.rb index fa12c026..6b170342 100644 --- a/lib/hamlit/compiler/script_compiler.rb +++ b/lib/hamlit/compiler/script_compiler.rb @@ -1,4 +1,5 @@ require 'hamlit/static_analyzer' +require 'hamlit/string_interpolation' module Hamlit class Compiler diff --git a/lib/hamlit/string_interpolation.rb b/lib/hamlit/string_interpolation.rb new file mode 100644 index 00000000..b95d36a7 --- /dev/null +++ b/lib/hamlit/string_interpolation.rb @@ -0,0 +1,60 @@ +require 'ripper' + +module Hamlit::StringInterpolation + class << self + # `code` param must be valid string literal + def compile(code) + [].tap do |exps| + tokens = Ripper.lex(code.strip) + + raise Hamlit::InternalError if tokens.size < 2 + strip_quotes!(tokens) + + compile_tokens!(exps, tokens) + end + end + + private + + def strip_quotes!(tokens) + _, type, _ = tokens.shift + raise Hamlit::InternalError if type != :on_tstring_beg + + _, type, _ = tokens.pop + raise Hamlit::InternalError if type != :on_tstring_end + end + + def compile_tokens!(exps, tokens) + until tokens.empty? + _, type, str = tokens.shift + + case type + when :on_tstring_content + exps << [:static, str] + when :on_embexpr_beg + embedded = shift_balanced_embexpr(tokens) + exps << [:dynamic, embedded] unless embedded.empty? + end + end + end + + def shift_balanced_embexpr(tokens) + String.new.tap do |embedded| + embexpr_open = 1 + + until tokens.empty? + _, type, str = tokens.shift + case type + when :on_embexpr_beg + embexpr_open += 1 + when :on_embexpr_end + embexpr_open -= 1 + break if embexpr_open == 0 + end + + embedded << str + end + end + end + end +end diff --git a/test/hamlit/string_interpolation_test.rb b/test/hamlit/string_interpolation_test.rb new file mode 100644 index 00000000..fc6720b7 --- /dev/null +++ b/test/hamlit/string_interpolation_test.rb @@ -0,0 +1,41 @@ +describe Hamlit::StringInterpolation do + describe '.compile' do + def assert_compile(expected, code) + actual = Hamlit::StringInterpolation.compile(code) + assert_equal expected, actual + end + + it { assert_compile([], %q|''|) } + it { assert_compile([], %q|""|) } + it { assert_compile([[:static, 'hello']], %q|"hello"|) } + it { assert_compile([[:static, 'hello '], [:static, 'world']], %q|"hello #{}world"|) } + it { assert_compile([[:dynamic, 'hello']], %q|"#{hello}"|) } + it { assert_compile([[:static, 'nya'], [:dynamic, '123']], %q|"nya#{123}"|) } + it { assert_compile([[:dynamic, '()'], [:static, '()']], %q|"#{()}()"|) } + it { assert_compile([[:static, ' '], [:dynamic, %q[ " #{ '#{}' } " ]]], %q|" #{ " #{ '#{}' } " }"|) } + it { assert_compile([[:static, 'a'], [:dynamic, 'b'], [:static, 'c'], [:dynamic, 'd'], [:static, 'e']], %q|%Q[a#{b}c#{d}e]|) } + it { assert_compile([[:static, 'a#{b}c#{d}e']], %q|%q[a#{b}c#{d}e]|) } + it { assert_compile([[:static, '\#{}'], [:dynamic, '123']], %q|"\#{}#{123}"|) } + it { assert_compile([[:dynamic, " '}' "]], %q|"#{ '}' }"|) } + + describe 'invalid argument' do + it 'raises internal error' do + assert_raises Hamlit::InternalError do + Hamlit::StringInterpolation.compile('1') + end + end + + it 'raises internal error' do + assert_raises Hamlit::InternalError do + Hamlit::StringInterpolation.compile('[]') + end + end + + it 'raises internal error' do + assert_raises Hamlit::InternalError do + Hamlit::StringInterpolation.compile('"]') + end + end + end + end +end