Re: [PATCH v6] regexp-replace and ^, word boundary or look-behind operators (and more).

Mikael Magnusson <[email protected]>
Newsgroups gmane.comp.shells.zsh.devel
Message-ID <CAHYJk3QBHeR9TTGtpRu4SapMAWo9OC-YQ71cO8aSDcuJ8QEp_Q@mail.gmail.com>
Is this v6 version still applicable?

On Sun, Mar 10, 2024 at 8:52 PM Stephane Chazelas <[email protected]> wrote:
>
> 2024-03-09 13:03:10 +0000, Stephane Chazelas:
> [...]
> > I'll send a v6 likely using namespaced variables rather than
> > going back to using positional parameters, once I understand the
> > point of using .regexp_replace.myvar over _regexp_replace_myvar
> [...]
>
> So here it is. I ended up using none of the new features
> (nameref and namespace) as they were not overly useful in this
> instance and that means the code can be used as-is in older
> versions. I'm using $_regexp_replace_localvarname for
> namespacing. I compared performance with
> ${.regexp_replace.localvarname} and they were similar (the
> latter about 2-3% slower in my limited tests).
>
> diff --git a/Functions/Misc/regexp-replace b/Functions/Misc/regexp-replace
> index d4408f0f7..630a5ceab 100644
> --- a/Functions/Misc/regexp-replace
> +++ b/Functions/Misc/regexp-replace
> @@ -1,91 +1,99 @@
> -# Replace all occurrences of a regular expression in a variable.  The
> -# variable is modified directly.  Respects the setting of the
> -# option RE_MATCH_PCRE.
> +# Replace all occurrences of a regular expression in a scalar variable.
> +# The variable is modified directly.  Respects the setting of the option
> +# RE_MATCH_PCRE, but otherwise sets the zsh emulation mode.
>  #
> -# First argument: *name* (not contents) of variable.
> -# Second argument: regular expression
> -# Third argument: replacement string.  This can contain all forms of
> -# $ and backtick substitutions; in particular, $MATCH will be replaced
> -# by the portion of the string matched by the regular expression.
> -
> -# we use positional parameters instead of variables to avoid
> -# clashing with the user's variable. Make sure we start with 3 and only
> -# 3 elements:
> -argv=("$1" "$2" "$3")
> -
> -# $4 records whether pcre is enabled as that information would otherwise
> -# be lost after emulate -L zsh
> -4=0
> -[[ -o re_match_pcre ]] && 4=1
> +# Arguments:
> +#
> +# 1. *name* (not contents) of variable or more generally any lvalue;
> +#    expected to be scalar.
> +#
> +# 2. regular expression
> +#
> +# 3. replacement string.  This can contain all forms of
> +#    $ and backtick substitutions; in particular, $MATCH will be
> +#    replaced by the portion of the string matched by the regular
> +#    expression. Parsing errors are fatal to the shell process.
> +
> +if (( $# < 2 || $# > 3 )); then
> +  setopt localoptions functionargzero
> +  print -ru2 "Usage: $0 <varname> <regexp> [<replacement>]"
> +  return 2
> +fi
> +
> +local _regexp_replace_use_pcre=0
> +[[ -o re_match_pcre ]] && _regexp_replace_use_pcre=1
>
>  emulate -L zsh
>
> +local _regexp_replace_subject=${(P)1} \
> +      _regexp_replace_regexp=$2 \
> +      _regexp_replace_replacement=$3 \
> +      _regexp_replace_result \
> +      MATCH MBEGIN MEND
>
> -local MATCH MBEGIN MEND
>  local -a match mbegin mend
>
> -if (( $4 )); then
> +if (( _regexp_replace_use_pcre )); then
>    # if using pcre, we're using pcre_match and a running offset
>    # That's needed for ^, \A, \b, and look-behind operators to work
>    # properly.
>
>    zmodload zsh/pcre || return 2
> -  pcre_compile -- "$2" && pcre_study || return 2
> +  pcre_compile -- "$_regexp_replace_regexp" && pcre_study || return 2
> +
> +  local _regexp_replace_offset=0 _regexp_replace_start _regexp_replace_stop _regexp_replace_new ZPCRE_OP
> +  local -a _regexp_replace_finds
>
> -  # $4 is the current *byte* offset, $5, $6 reserved for later use
> -  4=0 6=
> +  while pcre_match -b -n $_regexp_replace_offset -- "$_regexp_replace_subject"; do
> +    # we need to perform the evaluation in a scalar assignment so that
> +    # if it generates an array, the elements are converted to string (by
> +    # joining with the first chararacter of $IFS as usual)
> +    _regexp_replace_new=${(Xe)_regexp_replace_replacement}
>
> -  local ZPCRE_OP
> -  while pcre_match -b -n $4 -- "${(P)1}"; do
> -    # append offsets and computed replacement to the array
> -    # we need to perform the evaluation in a scalar assignment so that if
> -    # it generates an array, the elements are converted to string (by
> -    # joining with the first character of $IFS as usual)
> -    5=${(e)3}
> -    argv+=(${(s: :)ZPCRE_OP} "$5")
> +    _regexp_replace_finds+=( ${(s[ ])ZPCRE_OP} "$_regexp_replace_new" )
>
>      # for 0-width matches, increase offset by 1 to avoid
>      # infinite loop
> -    4=$((argv[-2] + (argv[-3] == argv[-2])))
> +    (( _regexp_replace_offset = _regexp_replace_finds[-2] + (_regexp_replace_finds[-3] == _regexp_replace_finds[-2]) ))
>    done
>
> -  (($# > 6)) || return # no match
> +  (( $#_regexp_replace_finds )) || return # no match
>
> -  set +o multibyte
> +  unsetopt multibyte
>
> -  # $5 contains the result, $6 the current offset
> -  5= 6=1
> -  for 2 3 4 in "$@[7,-1]"; do
> -    5+=${(P)1[$6,$2]}$4
> -    6=$(($3 + 1))
> +  _regexp_replace_offset=1
> +  for _regexp_replace_start _regexp_replace_stop _regexp_replace_new in "$_regexp_replace_finds[@]"; do
> +    _regexp_replace_result+=${_regexp_replace_subject[_regexp_replace_offset,_regexp_replace_start]}$_regexp_replace_new
> +    (( _regexp_replace_offset = _regexp_replace_stop + 1 ))
>    done
> -  5+=${(P)1[$6,-1]}
> -else
> +  _regexp_replace_result+=${_regexp_replace_subject[_regexp_replace_offset,-1]}
> +
> +else # no PCRE
>    # in ERE, we can't use an offset so ^, (and \<, \b, \B, [[:<:]] where
>    # available) won't work properly.
>
> -  # $4 is the string to be matched
> -  4=${(P)1}
> -
> -  while [[ -n $4 ]]; do
> -    if [[ $4 =~ $2 ]]; then
> -      # append initial part and substituted match
> -      5+=${4[1,MBEGIN-1]}${(e)3}
> -      # truncate remaining string
> -      if ((MEND < MBEGIN)); then
> -        # zero-width match, skip one character for the next match
> -        ((MEND++))
> -       5+=${4[1]}
> -      fi
> -      4=${4[MEND+1,-1]}
> -      # indicate we did something
> -      6=1
> -    else
> -      break
> +  local _regexp_replace_ok=0
> +  while [[ $_regexp_replace_subject =~ $_regexp_replace_regexp ]]; do
> +    # append initial part and substituted match
> +    _regexp_replace_result+=$_regexp_replace_subject[1,MBEGIN-1]${(Xe)_regexp_replace_replacement}
> +    # truncate remaining string
> +    if (( MEND < MBEGIN )); then
> +      # zero-width match, skip one character for the next match
> +      (( MEND++ ))
> +      _regexp_replace_result+=$_regexp_replace_subject[MBEGIN]
>      fi
> +    _regexp_replace_subject=$_regexp_replace_subject[MEND+1,-1]
> +    _regexp_replace_ok=1
> +    [[ -z $_regexp_replace_subject ]] && break
>    done
> -  [[ -n $6 ]] || return # no match
> -  5+=$4
> +  (( _regexp_replace_ok )) || return
> +  _regexp_replace_result+=$_regexp_replace_subject
>  fi
>
> -eval $1=\$5
> +# assign result to target variable if at least one substitution was
> +# made.  At this point, if the variable was originally array or assoc, it
> +# is converted to scalar. If $1 doesn't contain a valid lvalue
> +# specification, an exception is raised (exits the shell process if
> +# non-interactive).
> +: ${(P)1::="$_regexp_replace_result"}
> +
>
>
>


-- 
Mikael Magnusson
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.