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

module Rouge
  module Lexers
    class Idris < RegexLexer
      title "Idris"
      desc "The Idris programming language (idris-lang.org)"

      tag 'idris'
      aliases 'idr'
      filenames '*.idr'
      mimetypes 'text/x-idris'

      def self.reserved_keywords
        @reserved_keywords ||= %w(
          _ data class instance namespace
          infix[lr]? let where of with type
          do if then else case in
        )
      end

      def self.ascii
        @ascii ||= %w(
          NUL SOH [SE]TX EOT ENQ ACK BEL BS HT LF VT FF CR S[OI] DLE
          DC[1-4] NAK SYN ETB CAN EM SUB ESC [FGRU]S SP DEL
        )
      end

      def self.prelude_functions
        @prelude_functions ||= %w(
          abs acos all and any asin atan atan2 break ceiling compare concat
          concatMap const cos cosh curry cycle div drop dropWhile elem
          encodeFloat enumFrom enumFromThen enumFromThenTo enumFromTo exp
          fail filter flip floor foldl foldl1 foldr foldr1 fromInteger fst
          gcd getChar getLine head id init iterate last lcm length lines log
          lookup map max maxBound maximum maybe min minBound minimum mod
          negate not null or pi pred print product putChar putStr putStrLn
          readFile recip repeat replicate return reverse scanl scanl1 sequence
          sequence_ show sin sinh snd span splitAt sqrt succ sum tail take
          takeWhile tan tanh uncurry unlines unwords unzip unzip3 words
          writeFile zip zip3 zipWith zipWith3
        )
      end

      state :basic do
        rule %r/\s+/m, Text
        rule %r/{-#/, Comment::Preproc, :comment_preproc
        rule %r/{-/, Comment::Multiline, :comment
        rule %r/^\|\|\|.*$/, Comment::Doc
        rule %r/--(?![!#\$\%&*+.\/<=>?@\^\|_~]).*?$/, Comment::Single
      end

      # nested commenting
      state :comment do
        rule %r/-}/, Comment::Multiline, :pop!
        rule %r/{-/, Comment::Multiline, :comment
        rule %r/[^-{}]+/, Comment::Multiline
        rule %r/[-{}]/, Comment::Multiline
      end
      state :comment_preproc do
        rule %r/-}/, Comment::Preproc, :pop!
        rule %r/{-/, Comment::Preproc, :comment
        rule %r/[^-{}]+/, Comment::Preproc
        rule %r/[-{}]/, Comment::Preproc
      end

      state :directive do
        rule %r/\%(default)\s+(total|partial)/, Keyword  # totality
        rule %r/\%(access)\s+(public|abstract|private|export)/, Keyword  # export
        rule %r/\%(language)\s+(.*)/, Keyword  # language
        rule %r/\%(provide)\s+.*\s+(with)\s+/, Keyword  # type
      end

      state :prelude do
        rule %r/\b(Type|Exists|World|IO|IntTy|FTy|File|Mode|Dec|Bool|Ordering|Either|IsJust|List|Maybe|Nat|Stream|StrM|Not|Lazy|Inf)\s/, Keyword::Type
        rule %r/\b(Eq|Ord|Num|MinBound|MaxBound|Integral|Applicative|Alternative|Cast|Foldable|Functor|Monad|Traversable|Uninhabited|Semigroup|Monoid)\s/, Name::Class
        rule %r/\b(?:#{Idris.prelude_functions.join('|')})[ ]+(?![=:-])/, Name::Builtin
      end

      state :root do
        mixin :basic
        mixin :directive

        rule %r/\bimport\b/, Keyword::Reserved, :import
        rule %r/\bmodule\b/, Keyword::Reserved, :module
        rule %r/\b(?:#{Idris.reserved_keywords.join('|')})\b/, Keyword::Reserved
        rule %r/\b(Just|Nothing|Left|Right|True|False|LT|LTE|EQ|GT|GTE)\b/, Keyword::Constant
        # function signature
        rule %r/^[\w']+\s*:/, Name::Function
        # should be below as you can override names defined in Prelude
        mixin :prelude
        rule %r/[_a-z][\w']*/, Name
        rule %r/[A-Z][\w']*/, Keyword::Type

        # lambda operator
        rule %r(\\(?![:!#\$\%&*+.\\/<=>?@^\|~-]+)), Name::Function
        # special operators
        rule %r((<-|::|->|=>|=)(?![:!#\$\%&*+.\\/<=>?@^\|~-]+)), Operator
        # constructor/type operators
        rule %r(:[:!#\$\%&*+.\\/<=>?@^\|~-]*), Operator
        # other operators
        rule %r([:!#\$\%&*+.\\/<=>?@^\|~-]+), Operator

        rule %r/\d+(\.\d+)?(e[+-]?\d+)?/i, Num::Float
        rule %r/0o[0-7]+/i, Num::Oct
        rule %r/0x[\da-f]+/i, Num::Hex
        rule %r/\d+/, Num::Integer

        rule %r/'/, Str::Char, :character
        rule %r/"/, Str, :string

        rule %r/\[\s*\]/, Keyword::Type
        rule %r/\(\s*\)/, Name::Builtin

        # Quasiquotations
        rule %r/(\[)([_a-z][\w']*)(\|)/ do |m|
          token Operator, m[1]
          token Name, m[2]
          token Operator, m[3]
          push :quasiquotation
        end

        rule %r/[\[\](),;`{}]/, Punctuation
      end

      state :import do
        rule %r/\s+/, Text
        rule %r/"/, Str, :string
        # import X as Y
        rule %r/([A-Z][\w.]*)(\s+)(as)(\s+)([A-Z][a-zA-Z0-9_.]*)/ do
          groups(
            Name::Namespace, # X
            Text, Keyword, # as
            Text, Name # Y
          )
          pop!
        end

        # import X (functions)
        rule %r/([A-Z][\w.]*)(\s+)(\()/ do
          groups(
            Name::Namespace, # X
            Text,
            Punctuation # (
          )
          goto :funclist
        end

        rule %r/[\w.]+/, Name::Namespace, :pop!
      end

      state :module do
        rule %r/\s+/, Text
        # module X
        rule %r/([A-Z][\w.]*)/, Name::Namespace, :pop!
      end

      state :funclist do
        mixin :basic
        rule %r/[A-Z]\w*/, Keyword::Type
        rule %r/(_[\w\']+|[a-z][\w\']*)/, Name::Function
        rule %r/,/, Punctuation
        rule %r/[:!#\$\%&*+.\\\/<=>?@^\|~-]+/, Operator
        rule %r/\(/, Punctuation, :funclist
        rule %r/\)/, Punctuation, :pop!
      end

      state :character do
        rule %r/\\/ do
          token Str::Escape
          goto :character_end
          push :escape
        end

        rule %r/./ do
          token Str::Char
          goto :character_end
        end
      end

      state :character_end do
        rule %r/'/, Str::Char, :pop!
        rule %r/./, Error, :pop!
      end

      state :quasiquotation do
        rule %r/\|\]/, Operator, :pop!
        rule %r/[^\|]+/m, Text
        rule %r/\|/, Text
      end

      state :string do
        rule %r/"/, Str, :pop!
        rule %r/\\/, Str::Escape, :escape
        rule %r/[^\\"]+/, Str
      end

      state :escape do
        rule %r/[abfnrtv"'&\\]/, Str::Escape, :pop!
        rule %r/\^[\]\[A-Z@\^_]/, Str::Escape, :pop!
        rule %r/#{Idris.ascii.join('|')}/, Str::Escape, :pop!
        rule %r/o[0-7]+/i, Str::Escape, :pop!
        rule %r/x[\da-fA-F]+/i, Str::Escape, :pop!
        rule %r/\d+/, Str::Escape, :pop!
        rule %r/\s+\\/, Str::Escape, :pop!
      end
    end
  end
end
