lib/rouge/lexers/liquid.rb



# -*- coding: utf-8 -*- #
# frozen_string_literal: true

module Rouge
  module Lexers
    class Liquid < RegexLexer
      title "Liquid"
      desc 'Liquid is a templating engine for Ruby (liquidmarkup.org)'
      tag 'liquid'
      filenames '*.liquid'
      mimetypes 'text/html+liquid'

      state :root do
        rule %r/[^\{]+/, Text

        rule %r/(\{%-?)(\s*)/ do
          groups Comment::Preproc, Text::Whitespace
          push :logic
        end

        rule %r/(\{\{-?)(\s*)/ do
          groups Comment::Preproc, Text::Whitespace
          push :output
        end

        rule %r/\{/, Text
      end

      state :end_logic do
        rule(/(\s*)(-?%\})/) do
          groups Text::Whitespace, Comment::Preproc
          reset_stack
        end

        rule(/\n\s*/) do
          token Text::Whitespace
          if in_state? :liquid
            pop! until state? :liquid
          end
        end

        mixin :whitespace
      end

      state :end_output do
        rule(/(\s*)(-?\}\})/) do
          groups Text::Whitespace, Comment::Preproc
          reset_stack
        end
      end

      state :liquid do
        mixin :end_logic
        mixin :logic
      end

      state :logic do
        rule %r/(liquid)\b/, Name::Tag, :liquid

        # builtin logic blocks
        rule %r/(if|elsif|unless|case)\b/, Name::Tag, :condition
        rule %r/(when)\b/, Name::Tag, :value
        rule %r/(else|ifchanged|end\w+)\b/, Name::Tag, :end_logic
        rule %r/(break|continue)\b/, Keyword::Reserved, :end_logic

        # builtin iteration blocks
        rule %r/(for|tablerow)(\s+)(\S+)(\s+)(in)(\s+)/ do
          groups Name::Tag, Text::Whitespace, Name::Variable, Text::Whitespace,
                 Name::Tag, Text::Whitespace
          push :iteration_args
        end

        # other builtin blocks
        rule %r/(capture|(?:in|de)crement)(\s+)([a-zA-Z_](?:\w|-(?!%))*)/ do
          groups Name::Tag, Text::Whitespace, Name::Variable
          push :end_logic
        end

        rule %r/(comment)(\s*)(-?%\})/ do
          groups Name::Tag, Text::Whitespace, Comment::Preproc
          push :comment
        end

        rule %r/(raw)(\s*)(-?%\})/ do
          groups Name::Tag, Text::Whitespace, Comment::Preproc
          push :raw
        end

        # builtin tags
        rule %r/(assign|echo)\b/, Name::Tag, :assign

        rule %r/(include|render)(\s+)([\/\w-]+(?:\.[\w-]+)+\b)?/ do
          groups Name::Tag, Text::Whitespace, Text
          push :include
        end

        rule %r/(cycle)(\s+)(?:([\w-]+|'[^']*'|"[^"]*")(\s*)(:))?(\s*)/ do |m|
          token_class = case m[3]
                        when %r/'[^']*'/ then Str::Single
                        when %r/"[^"]*"/ then Str::Double
                        else
                          Name::Attribute
                        end
          groups Name::Tag, Text::Whitespace, token_class,
                 Text::Whitespace, Punctuation, Text::Whitespace
          push :tag_args
        end

        # custom tags or blocks
        rule %r/(\w+)\b/, Name::Tag, :block_args

        mixin :end_logic
      end

      state :output do
        rule %r/(\|)(\s*)/ do
          groups Punctuation, Text::Whitespace
          push :filters
          push :filter
        end

        mixin :end_output
        mixin :static
        mixin :variable
      end

      state :condition do
        rule %r/([=!]=|[<>]=?)/, Operator
        rule %r/(and|or|contains)\b/, Operator::Word

        mixin :value
      end

      state :value do
        mixin :end_logic
        mixin :static
        mixin :variable
      end

      state :iteration_args do
        rule %r/(reversed|continue)\b/, Name::Attribute
        rule %r/\(/, Punctuation, :range

        mixin :tag_args
      end

      state :block_args do
        rule %r/(\{\{-?)/, Comment::Preproc, :output_embed

        rule %r/(\|)(\s*)/ do
          groups Punctuation, Text::Whitespace
          push :filters
          push :filter
        end

        mixin :tag_args
      end

      state :tag_args do
        mixin :end_logic
        mixin :args
      end

      state :comment do
        rule %r/[^\{]+/, Comment

        rule %r/(\{%-?)(\s*)(endcomment)(\s*)(-?%\})/ do
          groups Comment::Preproc, Text::Whitespace, Name::Tag, Text::Whitespace, Comment::Preproc
          reset_stack
        end

        rule %r/\{/, Comment
      end

      state :raw do
        rule %r/[^\{]+/, Text

        rule %r/(\{%-?)(\s*)(endraw)(\s*)(-?%\})/ do
          groups Comment::Preproc, Text::Whitespace, Name::Tag, Text::Whitespace, Comment::Preproc
          reset_stack
        end

        rule %r/\{/, Text
      end

      state :assign do
        rule %r/=/, Operator
        rule %r/\(/, Punctuation, :range

        rule %r/(\|)(\s*)/ do
          groups Punctuation, Text::Whitespace
          push :filters
          push :filter
        end

        mixin :value
      end

      state :include do
        rule %r/(\{\{-?)/, Comment::Preproc, :output_embed
        rule %r/(with|for|as)\b/, Keyword::Reserved

        mixin :tag_args
      end

      state :output_embed do
        rule %r/(\|)(\s*)([a-zA-Z_](?:\w|-(?!}))*)/ do
          groups Punctuation, Text::Whitespace, Name::Function
        end

        rule %r/-?\}\}/, Comment::Preproc, :pop!

        mixin :args
      end

      state :range do
        rule %r/\.\./, Punctuation
        rule %r/\)/, Punctuation, :pop!

        mixin :whitespace
        mixin :number
        mixin :variable
      end

      state :filters do
        rule %r/(\|)(\s*)/ do
          groups Punctuation, Text::Whitespace
          push :filter
        end

        mixin :end_logic
        mixin :end_output
        mixin :args
      end

      state :filter do
        rule %r/[a-zA-Z_](?:\w|-(?![%}]))*/, Name::Function, :pop!
        
        mixin :whitespace
      end

      state :args do
        mixin :static

        rule %r/([a-zA-Z_][\w-]*)(\s*)(=|:)/ do
          groups Name::Attribute, Text::Whitespace, Operator
        end

        mixin :variable
      end

      state :static do
        rule %r/(false|true|nil)\b/, Keyword::Constant
        rule %r/'[^']*'/, Str::Single
        rule %r/"[^"]*"/, Str::Double
        rule %r/[,:]/, Punctuation

        mixin :whitespace
        mixin :number
      end

      state :whitespace do
        rule %r/\s+/, Text::Whitespace
        rule %r/#.*?(?=$|-?[}%]})/, Comment
      end

      state :number do
        rule %r/-/, Operator
        rule %r/\d+\.\d+/, Num::Float
        rule %r/\d+/, Num::Integer
      end

      state :variable do
        rule %r/(\.)(\s*)(first|last|size)\b(?![?!\/])/ do
          groups Punctuation, Text::Whitespace, Name::Function
        end

        rule %r/\.(?= *\w)|\[|\]/, Punctuation
        rule %r/(empty|blank|(for|tablerow)loop\.(parentloop\.)*\w+)\b(?![?!\/])/, Name::Builtin
        rule %r/[a-zA-Z_][\w-]*\b-?(?![?!\/])/, Name::Variable
        rule %r/\S+/, Text
      end
    end
  end
end