How do I visual select a calculation backwards? - regex

I would like to visual select backwards a calculation p.e.
200 + 3 This is my text -300 +2 + (9*3)
|-------------|*
This is text 0,25 + 2.000 + sqrt(15/1.5)
|-------------------------|*
The reason is that I will use it in insert mode.
After writing a calculation I want to select the calculation (using a map) and put the results of the calculation in the text.
What the regex must do is:
- select from the cursor (see * in above example) backwards to the start of the calculation
(including \/-+*:.,^).
- the calculation can start only with log/sqrt/abs/round/ceil/floor/sin/cos/tan or with a positive or negative number
- the calculation can also start at the beginning of the line but it never goes back to
a previous line
I tried in all ways but could not find the correct regex.
I noted that backward searching is different then forward searching.
Can someone help me?
Edit
Forgot to mention that it must include also the '=' if there is one and if the '=' is before the cursor or if there is only space between the cursor and '='.
It must not include other '=' signs.
200 + 3 = 203 -300 +2 + (9*3) =
|-------------------|<SPACES>*
200 + 3 = 203 -300 +2 + (9*3)
|-----------------|<SPACES>*
* = where the cursor is

A regex that comes close in pure vim is
\v\c\s*\zs(\s{-}(((sqrt|log|sin|cos|tan|exp)?\(.{-}\))|(-?[0-9,.]+(e-?[0-9]+)?)|([-+*/%^]+)))+(\s*\=?)?\s*
There are limitations: subexpressions (including function arguments) aren't parsed. You'd need to use a proper grammar parser to do that, and I don't recommend doing that in pure vim1
Operator Mapping
To enable using this a bit like text-objects, use something like this in your $MYVIMRC:
func! DetectExpr(flag)
let regex = '\v\c\s*\zs(\s{-}(((sqrt|log|sin|cos|tan|exp)?\(.{-}\))|(-?[0-9,.]+(e-?[0-9]+)?)|([-+*/%^]+)))+(\s*\=?)?\s*'
return searchpos(regex, a:flag . 'ncW', line('.'))
endf
func! PositionLessThanEqual(a, b)
"echo 'a: ' . string(a:a)
"echo 'b: ' . string(a:b)
if (a:a[0] == a:b[0])
return (a:a[1] <= a:b[1]) ? 1 : 0
else
return (a:a[0] <= a:b[0]) ? 1 : 0
endif
endf
func! SelectExpr(mustthrow)
let cpos = getpos(".")
let cpos = [cpos[1], cpos[2]] " use only [lnum,col] elements
let begin = DetectExpr('b')
if ( ((begin[0] == 0) && (begin[1] == 0))
\ || !PositionLessThanEqual(begin, cpos) )
if (a:mustthrow)
throw "Cursor not inside a valid expression"
else
"echoerr "not satisfied: " . string(begin) . " < " . string(cpos)
endif
return 0
endif
"echo "satisfied: " . string(begin) . " < " . string(cpos)
call setpos('.', [0, begin[0], begin[1], 0])
let end = DetectExpr('e')
if ( ((end[0] == 0) || (end[1] == 0))
\ || !PositionLessThanEqual(cpos, end) )
call setpos('.', [0, cpos[0], cpos[1], 0])
if (a:mustthrow)
throw "Cursor not inside a valid expression"
else
"echoerr "not satisfied: " . string(begin) . " < " . string(cpos) . " < " . string(end)
endif
return 0
endif
"echo "satisfied: " . string(begin) . " < " . string(cpos) . " < " . string(end)
norm! v
call setpos('.', [0, end[0], end[1], 0])
return 1
endf
silent! unmap X
silent! unmap <M-.>
xnoremap <silent>X :<C-u>call SelectExpr(0)<CR>
onoremap <silent>X :<C-u>call SelectExpr(0)<CR>
Now you can operator on the nearest expression around (or after) the cursor position:
vX - [v]isually select e[X]pression
dX - [d]elete current e[X]pression
yX - [y]ank current e[X]pression
"ayX - id. to register a
As a trick, use the following to arrive at the exact ascii art from the OP (using virtualedit for the purpose of the demo):
Insert mode mapping
In response to the chat:
" if you want trailing spaces/equal sign to be eaten:
imap <M-.> <C-o>:let #e=""<CR><C-o>"edX<C-r>=substitute(#e, '^\v(.{-})(\s*\=?)?\s*$', '\=string(eval(submatch(1)))', '')<CR>
" but I'm assuming you wanted them preserved:
imap <M-.> <C-o>:let #e=""<CR><C-o>"edX<C-r>=substitute(#e, '^\v(.{-})(\s*\=?\s*)?$', '\=string(eval(submatch(1))) . submatch(2)', '')<CR>
allows you to hit Alt-. during insert mode and the current expression gets replaced with it's evaluation. The cursor ends up at the end of the result in insert mode.
200 + 3 This is my text -300 +2 + (9*3)
This is text 0.25 + 2.000 + sqrt(15/1.5)
Tested by pressing Alt-. in insert 3 times:
203 This is my text -271
This is text 5.412278
For Fun: ascii art
vXoyoEsc`<jPvXr-r|e.
To easily test it yourself:
:let #q="vXoyo\x1b`<jPvXr-r|e.a*\x1b"
:set virtualedit=all
Now you can #q anywhere and it will ascii-decorate the nearest expression :)
200 + 3 = 203 -300 +2 + (9*3) =
|-------|*
|-------------------|*
200 + 3 = 203 -300 +2 + (9*3)
|-----------------|*
|-------|*
This is text 0,25 + 2.000 + sqrt(15/1.5)
|-------------------------|*
1 consider using Vim's python integration to do such parsing

This seems quite a complicated task after all to achieve with regex, so if you can avoid it in any way, try to do so.
I've created a regex that works for a few examples - give it a try and see if it does the trick:
^(?:[A-Za-z]|\s)+((?:[^A-Za-z]+)?(?:log|sqrt|abs|round|ceil|floor|sin|cos|tan)[^A-Za-z]+)(?:[A-Za-z]|\s)*$
The part that you are interested in should be in the first matching group.
Let me know if you need an explanation.
EDIT:
^ - match the beginning of a line
(?:[A-Za-z]|\s)+ - match everything that's a letter or a space once or more
match and capture the following 3:
((?:[^A-Za-z]+)? - match everything that's NOT a letter (i.e. in your case numbers or operators)
(?:log|sqrt|abs|round|ceil|floor|sin|cos|tan) - match one of your keywords
[^A-Za-z]+) - match everything that's NOT a letter (i.e. in your case numbers or operators)
(?:[A-Za-z]|\s)* - match everything that's a letter or a space zero or more times
$ - match the end of the line

Related

Regular expression for equations, variable number of inside parenthesis

I'm trying to write Regex for the case where I have series of equations, for example:
a = 2 / (1 + exp(-2*n)) - 1
a = 2 / (1 + e) - 1
a = 2 / (3*(1 + exp(-2*n))) - 1
In any case I need to capture content of the outer parenthesis, so 1 + exp(-2*n), 1+e and 3*(1 + exp(-2*n)) respectively.
I can write expression that will catch one of them, like:
\(([\w\W]*?\))\) will perfectly catch 1 + exp(-2*n)
\(([\w\W]*?)\) will catch 1+e
\(([\w\W]*?\))\)\) will catch 3*(1 + exp(-2*n))
But it seems silly to pass three lines of code for something such simple. How can I bundle it? Please take a note that I will be processing text (in loop) line-by-line anyway, so you don't have to bother for securing operator to not greedy take next line.
Edit:
Un-nested brackets are also allowed: a = 2 / (1 + exp(-2*n)) - (2-5)
The commented code below does not use regular expressions, but does parse char arrays in MATLAB and output the terms which contain top-level brackets.
So in your 3 question examples with a single set of nested brackets, it returns the outermost bracketed term.
In the example from your comment where there are two or more (possibly nested) terms within brackets at the "top level", it returns both terms.
The logic is as follows, see the comments for more details
Find the left (opening) and right (closing) brackets
Generate the "nest level" according to how many un-closed brackets there are at each point in the equation char
Find the indicies where the nesting level changes. We're interested in opening brackets where the nest level increases to 1 and closing brackets where it decreases from 1.
Extract the terms from these indices
e = { 'a = 2 / (1 + exp(-2*n)) - 1'
'a = 2 / (1 + e) - 1'
'a = 2 / (3*(1 + exp(-2*n))) - 1'
'a = 2 / (1 + exp(-2*n)) - (2-5)' };
str = cell(size(e)); % preallocate output
for ii = 1:numel(e)
str{ii} = parseBrackets_(e{ii});
end
function str = parseBrackets_( equation )
bracketL = ( equation == '(' ); % indicies of opening brackets
bracketR = ( equation == ')' ); % indicies of closing brackets
str = {}; % intialise empty output
if numel(bracketL) ~= numel(bracketR)
% Validate the input
warning( 'Could not match bracket pairs, count mismatch!' )
return
end
nL = cumsum( bracketL ); % cumulative open bracket count
nR = cumsum( bracketR ); % cumulative close bracket count
nestLevel = nL - nR; % nest level is number of open brackets not closed
nestLevelChanged = diff(nestLevel); % Get the change points in nest level
% get the points where the nest level changed to/from 1
level1L = find( nestLevel == 1 & [true,nestLevelChanged==1] ) + 1;
level1R = find( nestLevel == 1 & [nestLevelChanged==-1,true] );
% Compile cell array of terms within nest level 1 brackets
str = arrayfun( #(x) equation(level1L(x):level1R(x)), 1:numel(level1L), 'uni', 0 );
end
Outputs:
str =
{'1 + exp(-2*n)'}
{'1 + e'}
{'3*(1 + exp(-2*n))'}
{'1 + exp(-2*n)'} {'2-5'}

Further define a GAWK match and divide operation

I have some TXT files with numbers in them that I need to divide by 4.
Text-line I'm matching and changing is:-
scale = 23 23
My little GAWK file looks like this:-
/scale [\=] [0-9]+ [0-9]+/ {
$3 = int($3/4)
$4 = int($4/4) }
{print}
So I successfully get "scale = 5 5"
But, I have 3 more requirements, however, and would love some help...
1) the "scale" parameter should only be that following another match called "detail" on some lines above it.
(so instead of simply matching every "scale = " it would be "detail(.....)scale = ") (any number/letter/+newline between them)
2) these values of "scale" should never be lower than 1.
(dividing anything lower than 6 should always give a result of 1 (just changing "scale = 0" to "scale = 1" after will do))
3) values should preferably round up instead of down.
(so instead of 5 here from 23, it is actually 5.75 and should round up to 6 (this isn't SO important, but would be nice))
Something like this perhaps?
awk '/detail/ { d=1 }
d && /scale = [0-9]+ [0-9]+/ && $3>1 && $4>1 {
$3 = $3<6 ? 1 : sprintf("%1.0f", $3/4)
$4 = $4<5 ? 1 : sprintf("%1.0f", $4/4)
d = 0 }
1'
sprintf with a suitable format specifier applies rounding (see e.g. https://www.gnu.org/software/gawk/manual/html_node/Round-Function.html)
The ternary operator x ? y : z produces y if x is true, otherwise z.
Notice also the minor simplifications (= doesn't need a backslash or a character class, and {print} can be shortened to just 1).

How to insert text in a line that has an empty line above or below?

I have set of coordinates that are randomly separated with an empty line:
19.815857300 39.791813400
19.816105700 39.791921800
19.816220800 39.791984600
19.816271400 39.792010000
19.786375895 39.678097997
19.783813875 39.677022719
19.782758486 39.676590122
and so on...lot of coordinates :)
I want to insert a specific text at the beginning and at the end of the 1st coordinates set,( first line of the "paragraph").
The same for the last line of each paragraph - different text entry
The coordinates between them, also need another text before and after.
so.. the result I would like to achieve, should be something like
BEGIN_SEGMENT 0 50 10 19.815857300 39.791813400 0.00000000
SHAPE_POINT 19.816105700 39.791921800 0
SHAPE_POINT 19.816220800 39.791984600 0
END_SEGMENT 20 19.816271400 39.792010000 0.00000000
BEGIN_SEGMENT 0 50 10 19.786375895 39.678097997 0.00000000
SHAPE_POINT 19.783813875 39.677022719 0
END_SEGMENT 20 19.782758486 39.676590122 0.00000000
etc..etc..
Any ideas on how this could be done with notepad++ and regular expressions?
Thanx in advance!
Solution using python 3.6.
It reads in a file "input.txt" - line by line - until it gets an empty line.
On each start it recreates the file "result.txt".
Each found paragraph is appended to "result.txt" - until done.
Now you just need to get a python environment ;o) and feed it your file.
import re
import datetime
import random
# recreate empty file with timestamp
with open("result.txt","w") as r:
r.write(str(datetime.datetime.now()) + "\n")
with open("input.txt","r") as f:
while True:
paragraph = []
line = f.readline()
if line == "":
break # readline always retunrs \n on empty lines until file end reached
while len(line.strip()) > 0:
# accumulate lines
paragraph.append(line.strip())
line = f.readline()
if not paragraph:
continue # jumps back up to while True:
rv = []
for idx in range(0,len(paragraph)):
if idx == 0:
rv.append("BEGIN_SEGMENT 0 50 " + str(random.randint(1,10000)) + " " + paragraph[idx] + " 0.00000000 " + "\n")
elif idx != (len(paragraph) - 1):
rv.append("SHAPE_POINT " + paragraph[idx] + " 0" + "\n")
else:
rv.append("END_SEGMENT " + str(random.randint(1,10000)) + " " + paragraph[idx] + " 0.00000000" + "\n" + "\n")
# append results of paragraph
with open("result.txt","a") as resFile:
for line in rv:
resFile.write(line)
# edited code for random numbers instead of fixed ones
Not complete - you'll have to adjust the first and last rows by hand.
Step 1 end segment-begin segment
FIND:\r\n(\d.*)\r\n\r\n
REPLACE: \r\nEND_SEGMENT\t$1\r\n\r\nBEGIN_SEGMENT\t
Step 2: shape point
FIND: ^(\d.*)$
REPLACE: SHAPE_POINT\t$1
Assumes that there will be only one blank line between segments.
You can use https://regexper.com/ to display a diagram of the expressions.

In Vim, how to auto format current line when i typed semicolon in c++ files

For example: current line is
int i=0
After i typed semicolon,
int i = 0;
You'll need an inoremap on ; (and restricted to the current buffer -- :h :map-<buffer>). On the mapping, you'll have to parse the current line for (exactly?) one = sign. Reformat and be sure to move the cursor back.
The traps:
should the mapping be redoable? In that case you'll have to move the cursor with <c-g>U<left> and <right> as many times as required. Otherwise, a plain use of getline() + substitute() + setline() would work (and be quite simple actually) -> :call setline('.', substitute(getline('.'), '\s*[<>!=]\?=\s*', ' = ', 'g')).
if UTF-8 characters could appear like in auto head = "tĂȘte";, you won't be able to usestrlen()`.
Note, that clang-format is may be already able to do this kind of stuff -- but I cannot guarantee it permits to produce re-doable sequences.
" rtp/ftplugin/c/c_reformat_on_semincolon.vim
" The reodable version.
function! s:semicolon() abort
" let suppose the ';' is at the end of the line...
" and that there aren't multiple instructions of the same line
let res = ''
let line = getline('.')
let missing_before = match(line, '\V\S=')
let c = col('.') - 1
if missing_before >= 0
" prefer lh-vim-lib lh#encoding#strlen(line[missing_before : c])
let offset = c - missing_before " +/- 1 ?
let res .= repeat("\<c-g>U\<right>", offset)
\ . ' '
if line =~ '\V=\S'
let res .= "\<c-g>U\<left> "
let offset -= 1 " or 2 ?
endif
" let offset +/-= 1 ??
else
let offset = c - missing_after" +/- 1 ?
let res .= repeat("\<c-g>U\<right>", offset)
\ . ' '
endif
let res .= repeat("\<c-g>U\<left> ", offset)
return res
endfunction
inoremap <buffer> ; <c-r>=<sid>semicolon()<cr>
Note that this code is completely untested. You'll have to adjust offsets, and may be fix the logic.

How do I capture all occurences in a string in Vim?

I want to capture all certain occurrences in a string in Vimscript.
example:
let my_calculation = '200/3 + 23 + 100.5/3 -2 + 4*(200/2)'
How can I capture all numbers (including dots if there are) before and after the '/'? in 2 different variables:
- output before_slash: 200100.5200
- output after slash 332
How can I replace them if a condition occurs?
p.e. if after a single '/' there is no '.' add '.0' after this number
I tried to use matchstring and regex but after trying and trying I couldn't resolve it.
A useful feature that can be taken advantage of in this case is substitution
with an expression (see :help sub-replace-\=).
let [a; b] = [[]]
call substitute(s, '\(\d*\.\?\d\+\)/\(\d*\.\?\d\+\)\zs',
\ '\=add(a,submatch(1))[1:0]+add(b,submatch(2))[1:0]', 'g')
To answer the second part of the question:
let my_calculation = '200/3 + 23 + 100.5/3 -2 + 4*(200/2)'
echo substitute(my_calculation, '\(\/[0-9]\+\)\([^0-9.]\|$\)', '\1.0\2', 'g')
The above outputs:
200/3.0 + 23 + 100.5/3.0 -2 + 4*(200/2.0)
Give this a try:
function! GetNumbers(string)
let pairs = filter(split(a:string, '[^0-9/.]\+'), 'v:val =~ "/"')
let den = join(map(copy(pairs), 'matchstr(v:val, ''/\zs\d\+\(\.\d\+\)\?'')'), '')
let num = join(map(pairs, 'matchstr(v:val, ''\d\+\(\.\d\+\)\?\ze/'')'), '')
return [num, den]
endfunction
let my_calculation = '200/3 + 23 + 100.5/3 -2 + 4*(200/2)'
let [a,b] = GetNumbers(my_calculation)
echo a
echo b