Why `optional` in a Parser can err out - parsec

https://github.com/complyue/dcp is a minimum working example to reprod this error
$ cabal run dcp:dcp < samples/basic.txt
Up to date
dcp: 10:1:
|
10 | method doXXX() pass
| ^
unexpected 'm'
expecting ';'
CallStack (from HasCallStack):
error, called at src/Parser.hs:149:14 in main:Parser
$
I believe it's optionalSemicolon causing the failure:
https://github.com/complyue/dcp/blob/1df7ad590d78d4fa9a017eb53f9f265e291bdfa7/src/Parser.hs#L50-L54
findIt = do
-- ignore leading whitespaces and an optional semicolon in between
nbsc >> optionalSemicolon >> nbsc
-- try get a doc comment block
getIt >>= \case
And it's defined like this:
https://github.com/complyue/dcp/blob/1df7ad590d78d4fa9a017eb53f9f265e291bdfa7/src/Parser.hs#L31-L32
optionalSemicolon :: Parser Bool
optionalSemicolon = fromMaybe False <$> optional (True <$ symbol ";")
I can't reason about why it can fail like this.

Turns out it is because the symbol in optionalSemicolon is referencing sc while it shouldn't. Solved like this:
https://github.com/complyue/dcp/commit/fd2df02f7218e59db2a732d5de74acedfefefaa2?branch=fd2df02f7218e59db2a732d5de74acedfefefaa2&diff=unified#diff-1785501875711a0bda12ba99505aa188659de4e35ad27d7fb819993bd8ec95bdL26-R145
optionalComma :: Parser Bool
-optionalComma = fromMaybe False <$> optional (True <$ symbol ",")
+optionalComma = fromMaybe False <$> optional (True <$ string ",")
optionalSemicolon :: Parser Bool
-optionalSemicolon = fromMaybe False <$> optional (True <$ symbol ";")
+optionalSemicolon = fromMaybe False <$> optional (True <$ string ";")

Related

How to read a data file with some condition faster in Fortran?

I am trying to write down a Fortran subroutine for my code in order to read a data from a file (which is a huge data set on itself).The data file contains the Location (nx0,ny0,nz0) and the field related to that location (Bx,By,Bz).
(Ex: lets say the range for nx0, ny0 and nz0 is from [-15,15].
so the number of rows will be 31*31*31=29791)
-15.00000 -15.00000 -15.00000 700.00000 -590.00000 100.00000
-15.00000 -15.00000 -14.00000 -110.00000 -570.00000 100.00000
-15.00000 -15.00000 -13.00000 -550.00000 -200.00000 100.00000
-15.00000 -15.00000 -12.00000 -540.00000 -230.00000 100.00000
-15.00000 -15.00000 -11.00000 -140.00000 -50.00000 100.00000
. . . . . .
. . . . . .
. . . . . .
15.00000 15.00000 15.00000 140.00000 50.00000 100.000
What I want to do is to look for a specific location within my file (xi,yi and zi) and read the field related to that location then use it for further analysis. Not only the related field to the target position itself but also the surrounding field of that location (Like the three other side of the square around the target point).
subroutine read_data(xi,yi,zi,Bxij,Byij)
real*8,intent(in) :: xi,yi,zi !,time
real*8,intent(out) :: Bxij(4),Byij(4) !,Bzij(4)
integer,parameter :: step = 1 ,cols = 6, rows = 29791 !!!15,000,000
real,dimension(rows) :: nx0,ny0,nz0,Bx,By,Bz
character*15 filein
character*35 path_file
path_file = '/home/mehdi/Desktop/'
filein= 'test-0001'
open(7,file=trim(path_file)//filein, status='old',action='read')
xi_1 = xi +step
yi_1 = yi +step
do i = 1,rows
read(7,*) nx0(i),ny0(i),nz0(i),Bx(i),By(i),Bz(i)
c
if ( xi == nx0(i) .and. yi == ny0(i) .and.
& zi == nz0(i)) then
Bxij(1) = Bx(i)
Byij(1) = By(i)
cycle
endif
c
if ( xi == nx0(i) .and. yi_1 == ny0(i) .and.
& zi == nz0(i)) then
Bxij(2) = Bx(i)
Byij(2) = By(i)
cycle
endif
c
if ( xi_1 == nx0(i) .and. yi == ny0(i) .and.
& zi == nz0(i)) then
Bxij(3) = Bx(i)
Byij(3) = By(i)
cycle
endif
c
if ( xi_1 == nx0(i) .and. yi_1 == ny0(i) .and.
& zi == nz0(i)) then
Bxij(4) = Bx(i)
Byij(4) = By(i)
exit
endif
c
close(7)
enddo
end
I have done it this way but it is too slow. One of the most important things for me is the speed (which even for this small fraction of data set is really time consuming).
I know this slow mode is for the needs to read the whole data set each time in order to look for the target points. This subroutine is called couple times within the code and for the further steps the code is going to do the same thing over and over again, so it is time consuming.
How can I make this code work more efficiently?
Before I begin this answer, let me reiterate what I said in the comments to your question:
Do not underestimate how much data you can put into a single array. Reading once, and then having everything in memory is still the fastest way possible.
But let's assume that the data really gets too big.
Your main issue seems to be that you have to re-read all the data from the beginning until you find the value you're looking for. That takes the time.
If you can calculate which line of the data file the value you are interested in is, it might help to convert the file into an unformatted direct access file.
Here is an example code for the conversion. It's using Fortran 2008 features, so if your compiler can't do it, you have to modify it:
program convert
use iso_fortran_env, only: real64
implicit none
integer, parameter :: reclength = 6*8 ! Six 8-byte values
integer :: ii, ios
integer :: u_in, u_out
real(kind=real64) :: pos(3), B(3)
open(newunit=u_in, file='data.txt', form='formatted', &
status='old', action='read', access='sequential')
open(newunit=u_out, file='data.bin', form='unformatted', &
status='new', action='write', access='direct', recl=reclength)
ii = 0
do
ii = ii + 1
read(u_in, *, iostat=ios) pos, B
if (ios /= 0) exit
write(u_out, rec=ii) pos, B
end do
close(u_out)
close(u_in)
end program convert
Once you have converted the data, you can read only the record you need, as long as you can calculate which one it is. I have assumed that just like in your example, the z-coordinate changes fastest and the x-coordinate changes slowest.
program read_txt
use iso_fortran_env, only: real64
implicit none
integer, parameter :: nx=601, ny=181, nz=61
real(kind=real64), parameter :: x_min=real(-nx/2, kind=real64)
real(kind=real64), parameter :: y_min=real(-ny/2, kind=real64)
real(kind=real64), parameter :: z_min=real(-nz/2, kind=real64)
real(kind=real64), parameter :: x_step = 1.0_real64
real(kind=real64), parameter :: y_step = 1.0_real64
real(kind=real64), parameter :: z_step = 1.0_real64
real(kind=real64) :: request(3), pos(3), B(3)
integer :: ios, u_in
integer :: ii, jj, kk, record
integer, parameter :: reclength = 6 * 8 ! Six 8-byte values
open(newunit=u_in, file='data.bin', access='direct', form='unformatted', &
status='old', action='read', recl=reclength)
mainloop : do
read(*, *, iostat=ios) request
if (ios /= 0) exit mainloop
write(*, '(A, 3F7.2)') 'searching for ', request
! Calculate record
ii = nint((request(1)-x_min)/x_step)
jj = nint((request(2)-y_min)/y_step)
kk = nint((request(3)-z_min)/z_step)
record = kk + jj * nz + ii * nz * ny + 1
read(u_in, rec=record, iostat=ios) pos, B
if (ios /= 0) then
print *, 'failure to read'
cycle mainloop
end if
write(*, '(2(A, 3F7.2))') "found pos: ", pos, " Bx, By, Bz: ", B
end do mainloop
close(u_in)
end program read_txt
Note that the unformatted is not compiler- and system independent. A file created on one computer or with a program compiled by one compiler might not be able to be read with another program or on another computer.
But if you have control over it, it might be a useful way to speed things up.
PS: I left the x, y, and z coordinates in the file so that you can check whether the values are actually what you wanted. Always good to verify these things.

Elm: How to log inside a foldl

I have the following code:
findPerson name peeps = List.foldl
(\a b -> case b of
Just _ -> b
Nothing -> if a.name == name then
Just a
else Nothing
) Nothing peeps
I would like to log the values of a and b inside the foldl. I've tried:
findPerson : String -> List Person -> Maybe Person
findPerson name peeps = List.foldl
(\a b ->
Debug.log(a)
Debug.log(b)
case b of
Just _ -> b
Nothing -> if a.name == name then
Just a
else Nothing
) Nothing peeps
However, this throws an error
I am looking for one of the following things:
a closing paren ')'
whitespace`
What am I doing wrong, and how can I log the values inside foldl?
You can use a let in block for debugging.
let
_ = Debug.log "a" a
_ = Debug.log "b" b
in
case b of
...
A function (or lambda) can only return once.
Debug.log returns the second argument unchanged, so you have to pattern match it against something - and because you don't need the argument twice, but the side effect of Debug.log, you can pattern match it against _ (ignore).
You can also put the Debug.log directly inside the case statement, or inside the if statement for the same reasons #farmio mentioned :) - Like so :
findPerson name peeps = List.foldl
(\a b ->
case ( Debug.log "inspect b: " b ) of
Just _ ->
b
Nothing ->
if ( Debug.log "person name is: " a.name ) == name then
Just a
else
Nothing
) Nothing peeps
Not as clean, but sometimes more useful because is more compact.

Cut Special String - VBA

my Question is how to check if a string have a "text" & "_" at beginning.
For Example:
If sText = test.docx Then Function = False
ElseIF sText = Test_test.docx Then Function = True
End If
how i cut this string correctly, also when the text before the _ is not test and if there are several _ in the string it also works
use Instr() as shown here:
foo="test"&"_"
bar="test_test.docx"
if Instr(bar, foo)>0 then function = true
else function = false
end if
Instr(bar,foo) shows position of substring foo in string bar.
If there s no such substring, then it returns zero
If you need to check any text, that is not a problem, use this condition:
foo="_"
n=4
bar"test_textt.docx"
m=Instr(bar,foo)
if (m>n)and(len(bar)>m) then function=true
else function=false
end if
here n - number of characters, that would be before ""
If you dont know how many characters there may be, just set n to 0 or 1
if "" migth be last character, then delete condition (len(bar)>m)
You can simply check if the string begin with test_
dim res as boolean, filename as string
res = false
filename = ""
' if the len is not Superior to 5 (len of test_), don't check
if len(sText) > 5 then
' if the left part begin with test_
if left(lcase(sText), 5) = "test_" then
res = true
' if you want to retrieve the filename without test
filename = mid(sText, 6)
end if
end if

Bound expressions inside a condition in a cond block in Elixir?

Is there any way to have multiple bound expressions in a condition in a cond block similar to:
cond do
a = 1; a * 2 == 2 -> "Yes"
end
?
I guess it would be possible if there were something in Elixir like "let in" binding expressions as in Haskell:
let a = 1 in a * 2
Update
In the case below I'd like to bind the String.replace expression to a variable to increase readability (I could of course do it outside the cond which isn't an optimal solution for obvious reasons). The condition checks if the input is upper case only (apart from non-alphabetic characters) and is taken from an exercism.io challenge:
String.upcase(String.replace(input, ~r/[^A-Za-z]/, "")) == String.replace(input, ~r/[^A-Za-z]/, "") and String.length(String.replace(input, ~r/[^A-Za-z]/, "")) > 0 -> "Whoa, chill out!"
To directly answer the first question, the answer is yes. Your original code:
cond do
a = 1; a * 2 == 2 -> "Yes"
end
Is parsed as:
cond do
a = 1
a * 2 == 2 -> "Yes"
end
Because ; means the end of the whole expression. In case you want to include multiple expressions, use parens:
cond do
(a = 1; a * 2 == 2) -> "Yes"
end
Regarding your original question, I think there's nothing wrong with extracting a variable here:
def myfun(input) do
sanitized = String.replace(input, ~r/[^A-Za-z]/, "")
cond do
String.upcase(sanitized) == sanitized and String.length(sanitized) > 0 -> "Whoa, chill out!"
true -> "Good job"
end
end
You could also use the match operator = inside the pattern, more specifically you can use it in place of the first occurrence, but I think it is uglier and harder to read:
def myfun(input) do
cond do
String.upcase(sanitized = String.replace(input, ~r/[^A-Za-z]/, "")) == sanitized and String.length(sanitized) > 0 -> "Whoa, chill out!"
true -> "Good job"
end
end
However your code can be improved by closely looking at the comparisons you are doing. To match the condition, the input string must consist of only uppercase and non-alphabetic characters, containing at least one uppercase character. Since you are already using regexes, here's one that will match all of this in a single check:
\A[^a-z]*[A-Z][^a-z]*\z
Explanation:
\A and \z match string boundaries, the entire string must fulfill the condition
[^a-z]* zero or more non-lowercase characters, followed by
[A-Z] an uppercase character somewhere in the string, followed by
[^a-z]* zero or more non-lowercase characters
Code:
def myfun(input) do
cond do
Regex.match?(~r/\A[^a-z]*[A-Z][^a-z]*\z/, input) -> "Whoa, chill out!"
true -> "Good job"
end
end

'If' statement doesn't return what it's meant to

Am I just being blind or does this if statement genuinely not do what it's meant to?
Dim textSample as String = "F"
If Not textSample = "D" Or Not textSample = "E" Or Not textSample = "F" Then
MessageBox.Show("False")
End If
This displays the message box even though textSample is one of the letters. In my eyes that if statement should see that textSample is one of those letters and skip it, whereas if it was Z it would "Not" be equal to any of those and would therefore show the message box.
Why does it step into the if statement?
cond1 Or cond2 Or ... Or condn is true if and only if at least one of the given conditions are true. In your case it is always the case that at least one of the conditions is true (in fact at least two of the conditions will be true in each case). For example if textSample is "D" then the condition Not textSample = "E" and the condition Not textSample = "F" will be true. So the whole condition will be true.
Long story short: Use And instead of Or.
It is acting normally. True Or True Or False = True
I believe what you want is
Dim tBadLetters() As String = {"D", "E", "F"}
If Not tBadLetters.COntains(txtSample)
MsgBox("blah")
End If
It's because your using an OR clause, you need to use AND. Basically your saying if the textSample is not D then show your message box.
Change it to:
Dim textSample as String = "F"
If Not textSample = "D" AND Not textSample = "E" AND Not textSample = "F" Then
MessageBox.Show("False")
End If
That should work.
There is no value of textSample for which your if condition could possibly be false. I think you want this instead:
If Not (textSample = "D" Or textSample = "E" Or textSample = "F") Then
MessageBox.Show("False")
If you don't see the difference, examine the truth tables for both versions.
I would personally write it like so:
Dim textSample As String = "F"
If textSample <> "D" AndAlso textSample <> "E" AndAlso textSample <> "F" Then
MessageBox.Show("False")
End If
If you, like me, like to use the chainability of .NET, I also wrote for myself several String Extensions for cases like this:
Public Module StringExtensions
<Extension()> _
Public Function IsNullOrBlank(ByVal s As String) As Boolean
Return s Is Nothing OrElse s.Trim.Length.Equals(0)
End Function
<Extension()> _
Public Function IsNotNullOrBlank(ByVal s As String) As Boolean
Return s IsNot Nothing AndAlso s.Trim.Length > 0
End Function
<Extension()> _
Public Function IsEqualToAny(ByVal s As String, ByVal ParamArray values As String()) As Boolean
If s.IsNotNullOrBlank AndAlso values.Length > 0 Then
For Each value As String In values
If s = value Then Return True
Next
End If
Return False
End Function
<Extension()> _
Public Function IsNotEqualToAny(ByVal s As String, ByVal ParamArray values As String()) As Boolean
If s.IsNotNullOrBlank AndAlso values.Length > 0 Then
For Each value As String In values
If s = value Then Return False
Next
End If
Return True
End Function
End Module
To where I could then write your If statement like so:
Dim textSample As String = "F"
If textSample.IsNotEqualToAny("D", "E", "F") Then
MessageBox.Show("False")
End If
The message will always show. Here is why. In your example let us say textSample = "F". Then
if Not F equals D Or Not F equals E or Not F equals F
So let us summarize:
if (F not equals D ) or ( F not equals E ) or ( F not equals F)
...
if ( true ) or (true) or (false)
So your condition is true no matter what textSample is... (except if your textSample could be at the same be equals to "D" and equals to "E" and equals to "F").
I think you want to change the "or" to "and".