# frozen_string_literal: truebeginrequire'ripper'rescueLoadErrorendmoduleHaml# Compile [:dynamic, "foo#{bar}"] to [:multi, [:static, 'foo'], [:dynamic, 'bar']]classStringSplitter<Temple::Filterifdefined?(Ripper)&&RUBY_VERSION>="2.0.0"&&Ripper.respond_to?(:lex)class<<self# `code` param must be valid string literaldefcompile(code)[].tapdo|exps|tokens=Ripper.lex(code.strip)tokens.popwhiletokens.last&&[:on_comment,:on_sp].include?(tokens.last[1])iftokens.size<2raise(Haml::InternalError,"Expected token size >= 2 but got: #{tokens.size}")endcompile_tokens!(exps,tokens)endendprivatedefstrip_quotes!(tokens)_,type,beg_str=tokens.shiftiftype!=:on_tstring_begraise(Haml::InternalError,"Expected :on_tstring_beg but got: #{type}")end_,type,end_str=tokens.popiftype!=:on_tstring_endraise(Haml::InternalError,"Expected :on_tstring_end but got: #{type}")end[beg_str,end_str]enddefcompile_tokens!(exps,tokens)beg_str,end_str=strip_quotes!(tokens)untiltokens.empty?_,type,str=tokens.shiftcasetypewhen:on_tstring_contentbeg_str,end_str=escape_quotes(beg_str,end_str)exps<<[:static,eval("#{beg_str}#{str}#{end_str}").to_s]when:on_embexpr_begembedded=shift_balanced_embexpr(tokens)exps<<[:dynamic,embedded]unlessembedded.empty?endendend# Some quotes are split-unsafe. Replace such quotes with null characters.defescape_quotes(beg_str,end_str)case[beg_str[-1],end_str]when['(',')'],['[',']'],['{','}'][beg_str.sub(/.\z/){"\0"},"\0"]else[beg_str,end_str]endenddefshift_balanced_embexpr(tokens)String.new.tapdo|embedded|embexpr_open=1untiltokens.empty?_,type,str=tokens.shiftcasetypewhen:on_embexpr_begembexpr_open+=1when:on_embexpr_endembexpr_open-=1breakifembexpr_open==0endembedded<<strendendendenddefon_dynamic(code)return[:dynamic,code]unlessstring_literal?(code)return[:dynamic,code]ifcode.include?("\n")temple=[:multi]StringSplitter.compile(code).eachdo|type,content|casetypewhen:statictemple<<[:static,content]when:dynamictemple<<on_dynamic(content)endendtempleendprivatedefstring_literal?(code)returnfalseifSyntaxChecker.syntax_error?(code)type,instructions=Ripper.sexp(code)returnfalseiftype!=:programreturnfalseifinstructions.size>1type,_=instructions.firsttype==:string_literalendclassSyntaxChecker<RipperclassParseError<StandardError;enddefself.syntax_error?(code)self.new(code).parsefalserescueParseErrortrueendprivatedefon_parse_error(*)raiseParseErrorendendelse# Do nothing if ripper is unavailabledefcall(ast)astendendendend