13.15Regular expressions. 4tH 4tH Forth compiler
Hirdetés
Hirdetés
CHAPTER 13. SPECIAL LIBRARIES
13.15
Regular expressions
222
Well, we don’t offer full regular expressions (yet), but you can use wildcards for basic pattern matching. First you have to include it: include lib/wildcard.4th
You’ve probably used wildcards before. It is very easy. A ”*” stands for zero or more characters and a ”?” stands for a single character. E.g. if you’re looking for a line that begins with a date in the 21st century, then the word ”INVOICE” and finally ends with a name, you could try this: s" 20??-??-??
== INVOICE ==
*" mystring count wild-match
”WILD-MATCH” returns true if the string matches and false if the string doesn’t match, which is different from ”COMPARE”. Both strings are consumed, so save their address/count pair if you need them later on. Note that ”WILD-MATCH” is case sensitive, so if you need a case insensitive comparison you will have to convert them first.
Finally, ”WILD-MATCH” is faster than regular expressions, but also less precise. E.g.
”gr?y” will not only return ”grey” and ”gray”, but also ”groy”; ”reg*exp*” will not only return ”regular expressions”, but also ”registered express mail”.
13.16
String pattern matching
If that isn’t enough for you, 4tH also offers a small library for string pattern matching. Most regular expressions are static, that means they’re not entered at runtime, but hard-coded into the program. That means you’re dragging along the entire engine just to parse a string. 4tH offers almost the same possibilities, but you have to do a little more effort. Let’s say I want to see if a string contains my first name: include lib/chmatch.4th
: myname?
s" Hh" char-match >r s" Aa" char-match r> and >r s" Nn" char-match r> and >r s" Ss" char-match r> and
; s" Hans is the creator of 4tH" myname? . type cr
Of course, there are easier ways to do this, but you’ll see it works. ”CHAR-MATCH” will return a ”true” value if one of the characters specified matches and a ”false” value if it doesn’t. E.g.: s" Hans" s" Hh" char-match
Will return a ”true” value and ”ans”. On the other hand: s" Hans" s" Aa" char-match
CHAPTER 13. SPECIAL LIBRARIES 223
Will return a ”false” value and ”Hans”. Now you understand how it works: ”MYNAME?” chops off character by character until it is done. But what it I want to check whether ”Hans” appears in the middle of a string? Easy: include lib/chmatch.4th
: myname?
s" Hh" skip-until s" Hh" char-match >r s" Aa" char-match r> and >r s" Nn" char-match r> and >r s" Ss" char-match r> and
; s" In October 1994 Hans created 4tH" myname? . type cr
This will wind the string upto the first ”H” and then start scanning. But what if there’s another ”H” in the string? Again, you can handle that: include lib/chmatch.4th
: myname?
begin s" Hh" skip-until s" Hh" char-match >r s" Aa" char-match r> and >r s" Nn" char-match r> and >r s" Ss" char-match r> and >r dup 0<> r@ 0= and while r> drop repeat r>
; s" The 4tH compiler was created by Hans in 1994" myname? . type cr
You want to know whether it was at the very end of the line? Yes, you can: include lib/chmatch.4th
: myname?
begin s" Hh" skip-until s" Hh" char-match >r s" Aa" char-match r> and >r s" Nn" char-match r> and >r s" Ss" char-match r> and >r dup 0<> r@ 0= and while r> drop repeat r> over 0= and
; s" The 4tH compiler was created by Hans" myname? . type cr
Let’s try something more difficult now, let’s see if we can parse a number. The first part is very easy: include lib/chmatch.4th
s" 0123456789" sconstant ’digits’ s" 0endshere" ’digits’ char-match . type cr
CHAPTER 13. SPECIAL LIBRARIES 224
That works, the correct flag is returned and the string ”endshere” is returned. But a number may be of arbitrary size, how do we do that? Simple, after the first number we can simply discard any trailing digits: include lib/chmatch.4th
s" 0123456789" sconstant ’digits’ s" 0123456789endshere"
’digits’ char-match >r
’digits’ skip-while r> . type cr
Still, it returns the correct flag and the correct string. But a number may also come with an optional sign. Now that complicates life, does it? No it doesn’t: include lib/chmatch.4th
s" 0123456789" sconstant ’digits’ s" +-" sconstant ’sign’ s" -123456789endshere"
’sign’ char-match drop
’digits’ char-match >r
’digits’ skip-while r> . type cr
What? Aren’t we evaluating the flag? No, why should we. The sign is optional, isn’t it?
Note that ”CHAR-MATCH’ not only sets a flag, but also discards the character matched.
So it it isn’t a sign, it will simply remain there and be caught by the digit test. But we’ll take it a step further. What about a decimal point? A number may come with one or without one. And it may appear anywhere in the number. Now how we do that?
include lib/chmatch.4th
s" 0123456789" sconstant ’digits’ s" +-" sconstant ’sign’ char . constant ’point’ s" -123456.789endshere"
’sign’ char-match drop
’digits’ char-match >r
’digits’ skip-while
’point’ char-equal drop
’digits’ skip-while r> . type cr
When parsing stops, because the program reached the point where there are no more digits, we simply check for an optional decimal point. If it is there, we continue checking for digits. If it isn’t there, parsing stops. Note that the check for trailing digits fails too, but you may make it optional if you want, it won’t change the result: include lib/chmatch.4th
s" 0123456789" sconstant ’digits’ s" +-" sconstant ’sign’ char . constant ’point’
CHAPTER 13. SPECIAL LIBRARIES 225 s" -123456.789endshere"
’sign’ char-match drop
’digits’ char-match >r
’digits’ skip-while
’point’ char-equal if ’digits’ skip-while then r> . type cr
Note that ”CHAR-EQUAL” performs the same function as ”CHAR-MATCH”, it’s only more efficient if there is only one character to compare
15
. Our program still doesn’t allow for ”-.45”. Yes, you can add an optional test for that, but we don’t want ”-.45.67” to succeed. That one can be handled too: include lib/chmatch.4th
s" 0123456789" sconstant ’digits’ s" +-" sconstant ’sign’ char . constant ’point’ s" -.789.000endshere000."
’sign’ char-match drop
’point’ char-equal >r
’digits’ char-match r> swap >r >r
’digits’ skip-while r> 0= if ’point’ char-equal if ’digits’ skip-while then then r> . type cr
Now the test will only be performed if the previous test failed, which is exactly what we want. Try it. Where ever you place the dot, it won’t make any difference: it will always return ”.000endshere000.”. Unfortunately, that also applies to: s" -0..000endshere000."
If you require that a decimal point is always followed by a digit, simply enforce it: include lib/chmatch.4th
s" 0123456789" sconstant ’digits’ s" +-" sconstant ’sign’ char . constant ’point’ s" -0..000endshere000."
’sign’ char-match drop
’point’ char-equal >r
’digits’ char-match r> swap >r >r
’digits’ skip-while r> 0= if
’point’ char-equal if
’digits’ char-match r> and >r
’digits’ skip-while then then r> . type cr
Note this doesn’t change the string returned (up to the trailing point it was still a valid number), but it does change the flag: this is not a valid number!
I hope you see that 4tH can do some pretty sophisticated pattern matching with very little effort without dragging along an entire regular expression engine, either in code or in the compiler itself.
15
If you want to speed up a single character search, scanskip.4th offers the words you’re looking for.
Letöltés
Hirdetés