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

module Rouge
  module Lexers
    class Nix < RegexLexer
      title 'Nix'
      desc 'The Nix expression language (https://nixos.org/nix/manual/#ch-expression-language)'
      tag 'nix'
      aliases 'nixos'
      filenames '*.nix'

      id_boundary = /(?![a-zA-Z0-9_'-])/

      state :whitespaces do
        rule %r/^\s*\n\s*$/m, Text
        rule %r/\s+/, Text
      end

      state :comment do
        rule %r/#.*$/, Comment
        rule %r(/\*), Comment, :multiline_comment
      end

      state :multiline_comment do
        rule %r(\*/), Comment, :pop!
        rule %r/./, Comment
      end

      state :number do
        rule %r/[0-9]/, Num::Integer
      end

      state :null do
        rule %r/null#{id_boundary}/, Keyword::Constant
      end

      state :boolean do
        rule %r/(?:true|false)#{id_boundary}/, Keyword::Constant
      end

      state :binding do
        rule %r/[a-zA-Z_][a-zA-Z0-9_'-]*/, Name::Variable
      end

      state :path do
        word = "[a-zA-Z0-9\._-]+"
        section = "(\/#{word})"
        prefix = "[a-z\+]+:\/\/"
        root = /#{section}+/.source
        tilde = /~#{section}+/.source
        basic = /#{word}(\/#{word})+/.source
        url = /#{prefix}(\/?#{basic})/.source
        rule %r/(#{root}|#{tilde}|#{basic}|#{url})/, Str::Other
      end

      state :string do
        rule %r/"/, Str::Double, :double_quoted_string
        rule %r/''/, Str::Double, :indented_string
      end

      state :string_content do
        rule %r/\\./, Str::Escape
        rule %r/\$\$/, Str::Escape
        rule %r/\${/, Str::Interpol, :string_interpolated_arg
      end

      state :indented_string_content do
        rule %r/'''/, Str::Escape
        rule %r/''\$/, Str::Escape
        rule %r/\$\$/, Str::Escape
        rule %r/''\\./, Str::Escape
        rule %r/\${/, Str::Interpol, :string_interpolated_arg
      end

      state :string_interpolated_arg do
        mixin :expression
        rule %r/}/, Str::Interpol, :pop!
      end

      state :indented_string do
        mixin :indented_string_content
        rule %r/''/, Str::Double, :pop!
        rule %r/./, Str::Double
      end

      state :double_quoted_string do
        mixin :string_content
        rule %r/"/, Str::Double, :pop!
        rule %r/./, Str::Double
      end

      state :operator do
        rule %r/(\.|\?|\+\+|\+|!=|!|\/\/|\=\=|&&|\|\||->|\/|\*|-|<|>|<=|=>)/, Operator
      end

      state :assignment do
        rule %r/(=)/, Operator
        rule %r/(@)/, Operator
      end

      state :accessor do
        rule %r/(\$)/, Punctuation
      end

      state :delimiter do
        rule %r/(;|,|:)/, Punctuation
      end

      state :atom_content do
        mixin :expression
        rule %r/\)/, Punctuation, :pop!
      end

      state :atom do
        rule %r/\(/, Punctuation, :atom_content
      end

      state :list do
        rule %r/\[/, Punctuation, :list_content
      end

      state :list_content do
        rule %r/\]/, Punctuation, :pop!
        mixin :expression
      end

      state :set do
        rule %r/{/, Punctuation, :set_content
      end

      state :set_content do
        rule %r/}/, Punctuation, :pop!
        mixin :expression
      end

      state :expression do
        mixin :ignore
        mixin :comment
        mixin :boolean
        mixin :null
        mixin :number
        mixin :path
        mixin :string
        mixin :keywords
        mixin :operator
        mixin :accessor
        mixin :assignment
        mixin :delimiter
        mixin :binding
        mixin :atom
        mixin :set
        mixin :list
      end

      state :keywords do
        mixin :keywords_namespace
        mixin :keywords_declaration
        mixin :keywords_conditional
        mixin :keywords_reserved
        mixin :keywords_builtin
      end

      state :keywords_namespace do
        keywords = %w(with in inherit)
        rule %r/(?:#{keywords.join('|')})#{id_boundary}/, Keyword::Namespace
      end

      state :keywords_declaration do
        keywords = %w(let)
        rule %r/(?:#{keywords.join('|')})#{id_boundary}/, Keyword::Declaration
      end

      state :keywords_conditional do
        keywords = %w(if then else)
        rule %r/(?:#{keywords.join('|')})#{id_boundary}/, Keyword
      end

      state :keywords_reserved do
        keywords = %w(rec assert map)
        rule %r/(?:#{keywords.join('|')})#{id_boundary}/, Keyword::Reserved
      end

      state :keywords_builtin do
        keywords = %w(
          abort
          baseNameOf
          builtins
          derivation
          fetchTarball
          import
          isNull
          removeAttrs
          throw
          toString
        )
        rule %r/(?:#{keywords.join('|')})#{id_boundary}/, Keyword::Reserved
      end

      state :ignore do
        mixin :whitespaces
      end

      state :root do
        mixin :ignore
        mixin :expression
      end

      start do
      end
    end
  end
end
