packages feed

hydra-kernel-0.18.0: src/main/haskell/Hydra/Core/Print/Emacs/Regex.hs

-- Note: this is an automatically generated file. Do not edit.

-- | Per-dialect printer rendering the hydra.core.regex AST into GNU Emacs regexp syntax. Emacs inverts several escaping conventions relative to POSIX ERE (probe-verified, Emacs 28.2): alternation is \| (bare | is literal), grouping is \( \) (bare ( ) are literal), and bounded quantifiers are \{n,m\} (bare braces are literal). The quantifiers * + ? are bare as usual. Hydra's . (any incl. newline) renders as [^z-a] — an empty negated range that matches every character including newline (Emacs . excludes newline). Three further divergences, each probe-verified (issue #603): (1) anchors ^ and $ (whole-STRING boundaries in Hydra) render as the GNU whole-buffer anchors \` and \', because Emacs's native ^ and $ are line-oriented; (2) character-class metacharacters ] ^ - are disambiguated POSITIONALLY (] first, - last, ^ never first) rather than by backslash-escaping, because Emacs bracket expressions are POSIX-like and have no escape mechanism inside [...] (a backslash there is an ordinary character); (3) alternation branches are ordered by descending rendered length, because Emacs's alternation is leftmost-FIRST, unlike Hydra's leftmost-LONGEST semantics. See docs/specification/regex.md and issue #567/#603.

module Hydra.Core.Print.Emacs.Regex where

import qualified Hydra.Core.Ast as Ast
import qualified Hydra.Core.Coders as Coders
import qualified Hydra.Core.Diff as Diff
import qualified Hydra.Core.Docs as Docs
import qualified Hydra.Core.Error.Checking as Checking
import qualified Hydra.Core.Error.File as ErrorFile
import qualified Hydra.Core.Error.Model as ErrorModel
import qualified Hydra.Core.Error.Packaging as ErrorPackaging
import qualified Hydra.Core.Error.System as ErrorSystem
import qualified Hydra.Core.Errors as Errors
import qualified Hydra.Core.File as File
import qualified Hydra.Core.Graph as Graph
import qualified Hydra.Core.Json.Model as JsonModel
import qualified Hydra.Core.Overlay.Haskell.Lib.Equality as Equality
import qualified Hydra.Core.Overlay.Haskell.Lib.Lists as Lists
import qualified Hydra.Core.Overlay.Haskell.Lib.Literals as Literals
import qualified Hydra.Core.Overlay.Haskell.Lib.Logic as Logic
import qualified Hydra.Core.Overlay.Haskell.Lib.Pairs as Pairs
import qualified Hydra.Core.Overlay.Haskell.Lib.Strings as Strings
import qualified Hydra.Core.Markdown as Markdown
import qualified Hydra.Core.Model as Model
import qualified Hydra.Core.Packaging as Packaging
import qualified Hydra.Core.Parsing as Parsing
import qualified Hydra.Core.Paths as Paths
import qualified Hydra.Core.Query as Query
import qualified Hydra.Core.Regex as Regex
import qualified Hydra.Core.Relational as Relational
import qualified Hydra.Core.System as System
import qualified Hydra.Core.Tabular as Tabular
import qualified Hydra.Core.Testing as Testing
import qualified Hydra.Core.Time as Time
import qualified Hydra.Core.Topology as Topology
import qualified Hydra.Core.Typed as Typed
import qualified Hydra.Core.Typing as Typing
import qualified Hydra.Core.Util as Util
import qualified Hydra.Core.Validation as Validation
import qualified Hydra.Core.Variants as Variants
import Prelude hiding  (Enum, Ordering, decodeFloat, encodeFloat, fail, lines, map, pure, sum, unlines)
import qualified Data.Scientific as Sci
import Data.Void

-- | Render an alternation, joining its branches with Emacs's \| operator. Branches are ordered by descending rendered length so that Emacs's leftmost-first matching agrees with Hydra's leftmost-longest semantics for fixed-length alternatives.
alternation :: [[Regex.Quantified]] -> String
alternation alt =

      let rendered = Lists.map regexSequence alt
          byLength = Lists.sortBy Strings.length rendered
      in (Strings.join "\\|" (Lists.reverse byLength))

-- | The Emacs rendering of Hydra's . (any character incl. newline): the empty-negated-range [^z-a].
anyClass :: String
anyClass = "[^z-a]"

-- | Render a single atom; . -> [^z-a], and groups use Emacs's \( \) escaping.
atom :: Regex.Atom -> String
atom a =
    case a of
      Regex.AtomLiteral v0 -> escapeLiteral v0
      Regex.AtomAny -> anyClass
      Regex.AtomAnchorStart -> "\\`"
      Regex.AtomAnchorEnd -> "\\'"
      Regex.AtomGroup v0 -> Strings.concat [
        "\\(",
        (alternation v0),
        "\\)"]
      Regex.AtomClass v0 -> characterClass v0

-- | Render a character class, including the leading ^ for a negated class. Reorders items positionally for Emacs (which has no bracket-expression escaping): a literal ] is moved to the front (immediately after the optional negating ^), a literal - is moved to the back, and (only when there is no literal ] item to occupy the first slot) a literal ^ is moved after the first non-^ item -- Emacs treats a leading ^ as the negation marker regardless of Hydra's negated flag. The one residual gap: a class whose only member is a literal ^ has no safe position on Emacs; this is an extreme corner case (single-item classes are otherwise unremarkable) and is left unhandled here.
characterClass :: Regex.CharacterClass -> String
characterClass cc =

      let items = Regex.characterClassItems cc
          bracketAndRest = Lists.partition (classItemIsLiteral 93) items
          bracketItems = Pairs.first bracketAndRest
          afterBracket = Pairs.second bracketAndRest
          dashAndRest = Lists.partition (classItemIsLiteral 45) afterBracket
          dashItems = Pairs.first dashAndRest
          rest = Pairs.second dashAndRest
          caretAndNonCaret = Lists.partition (classItemIsLiteral 94) rest
          caretItems = Pairs.first caretAndNonCaret
          nonCaretItems = Pairs.second caretAndNonCaret
          safeRest =
                  Logic.ifElse (Logic.or (Logic.not (Lists.isEmpty bracketItems)) (Lists.isEmpty nonCaretItems)) rest (Lists.concat2 nonCaretItems caretItems)
          ordered =
                  Lists.concat [
                    bracketItems,
                    safeRest,
                    dashItems]
      in (Strings.concat [
        "[",
        (Logic.ifElse (Regex.characterClassNegated cc) "^" ""),
        (Strings.concat (Lists.map classItem ordered)),
        "]"])

-- | Render one character-class member (a single character or an inclusive range). Emacs bracket expressions do NOT support backslash escaping (probe-verified: \- and \] inside [...] are two literal characters, not an escape); the metacharacters ] ^ - are instead disambiguated positionally by characterClass (] first, - last, ^ never first), so a plain character or range here never needs an escape.
classItem :: Regex.ClassItem -> String
classItem item =
    case item of
      Regex.ClassItemCharacter v0 -> printCodePoint v0
      Regex.ClassItemRange v0 -> Strings.concat [
        printCodePoint (Regex.characterRangeFrom v0),
        "-",
        (printCodePoint (Regex.characterRangeTo v0))]

-- | True if a ClassItem is the single literal character code point c (not a range).
classItemIsLiteral :: Int -> Regex.ClassItem -> Bool
classItemIsLiteral c item =
    case item of
      Regex.ClassItemCharacter v0 -> Equality.equal v0 c
      _ -> False

-- | Render a literal code point in top-level context under Emacs regexp, escaping only the characters that are special bare in Emacs: . * + ? [ ] ^ $ \. Note | ( ) { } are literal in Emacs and are NOT escaped here.
escapeLiteral :: Int -> String
escapeLiteral c =

      let isMeta =
              Lists.foldl (\acc -> \m -> Logic.or acc (Equality.equal c m)) False [
                46,
                42,
                43,
                63,
                91,
                93,
                94,
                36,
                92]
      in (Logic.ifElse isMeta (Strings.concat2 "\\" (printCodePoint c)) (printCodePoint c))

-- | Render a single Unicode code point as a one-character string.
printCodePoint :: Int -> String
printCodePoint c = Strings.fromList [
  c]

-- | Render a hydra.core.regex AST into GNU Emacs regexp syntax (escaped \| \( \) \{..\}, . as [^z-a]).
printRegex :: [[Regex.Quantified]] -> String
printRegex r = alternation r

-- | Render an atom followed by its quantifier suffix.
quantified :: Regex.Quantified -> String
quantified qa = Strings.concat2 (atom (Regex.quantifiedAtom qa)) (quantifier (Regex.quantifiedQuantifier qa))

-- | Render a quantifier suffix; bounded {..} forms use Emacs's escaped braces \{..\}.
quantifier :: Regex.Quantifier -> String
quantifier q =
    case q of
      Regex.QuantifierOne -> ""
      Regex.QuantifierZeroOrOne -> "?"
      Regex.QuantifierZeroOrMore -> "*"
      Regex.QuantifierOneOrMore -> "+"
      Regex.QuantifierExactly v0 -> Strings.concat [
        "\\{",
        (Literals.printInt32 v0),
        "\\}"]
      Regex.QuantifierAtLeast v0 -> Strings.concat [
        "\\{",
        (Literals.printInt32 v0),
        ",\\}"]
      Regex.QuantifierRange_ v0 -> Strings.concat [
        "\\{",
        (Literals.printInt32 (Regex.quantifierRangeMin v0)),
        ",",
        (Literals.printInt32 (Regex.quantifierRangeMax v0)),
        "\\}"]

-- | Render a sequence of quantified atoms by concatenation.
regexSequence :: [Regex.Quantified] -> String
regexSequence s = Strings.concat (Lists.map quantified s)