String Fill and Match

Version 1.1 of Mini Micro included a couple of very useful additions to the stringUtil module. If you aren't already using the new fill and match methods, you probably should be!

string.fill

The new fill method is pretty straightfoward; you'll find a similar functionality in many other languages. The base string contains placeholders in curly brackets, and those are replaced with values from a list or map. (Note: I highly recommend you fire up Mini Micro and follow along with the examples below; it's by far the best way to learn.)

First let's look at the case where you want to fill in values from a list. In this case the placeholders should be index numbers, like so:

]import "stringUtil"
]s = "The {0} in {1} falls mainly on the {2}."
]values = ["rain", "Spain", "plain"]
]print s.fill(values)
The rain in Spain falls mainly on the plain.

This is very similar to composite strings in C#. It's also a little bit like C's printf, although better because the indexes do not have to appear in order (see "Localization" below for why this is important).

Alternatively, you can supply values to fill in from a map rather than a list. In this case the placeholders can be strings that match keys in the map.

]import "stringUtil"
]s = "Hello {user}, welcome to {here}!"
]values = {"user":"Jill", "here":"Mini Micro"}
]print s.fill(values)
Hello Jill, welcome to Mini Micro!

You could even specify locals as the source of values to fill in. In this case, your placeholders can be the name of any local variable:

]name = "Sam"
]age = 16
]print "Happy birthday, {name}!  Here are {age} candles.".fill(locals)
Happy birthday, Sam!  Here are 16 candles.

string.match

The match method does the opposite of fill. It takes a string pattern that contains some constant parts, and some variable parts; and an actual string to compare to. If the constant parts can be matched up to the actual string, then it returns a map with the values of the variable parts.

Let's illustrate by doing the inverse of each of the examples above. First, handle any stuff in any place that falls mainly on anything:

]pat = "The {0} in {1} falls mainly on the {2}."
]pat.match("The rain in Spain falls mainly on the plain.")
]s = "The rain in Spain falls mainly on the plain."
]print s.match(pat)
{"0": "rain", "1": "Spain", "2": "plain"}

First we defined the pattern pat, with some variable parts in curly braces. This is exactly the same pattern we used with fill above. Then we defined a string s that can fit this pattern. Calling s.match(pat) returns a map, showing what is in each of the variable spots. (Note that it's always a map, and always with string keys; you don't get a list out even if all the placeholders happen to be numbers.)

If the string can't be matched to the pattern, then match returns null:

]s = "The rain in Arizona rarely falls at all."
]print s.match(pat)
null

Now let's invert our second example.

]pat = "Hello {user}, welcome to {here}!"
]s = "Hello Jill, welcome to Mini Micro!"
]print s.match(pat)
{"user": "Jill", "here": "Mini Micro"}

Of course it's a little harder to imagine why you'd do this. A better example might be scraping data out of a file. Suppose you have some HTML data, and you want to find the title, defined as text that comes between "<h1>" and "</h1>". Then you might do something like:

]pat = "{}<h1>{title}</h1>{}"
]s = "<html><h1>Demo Time!</h1><p>More stuff here.</p></html>"
]s.match(pat)
{"": "<p>More stuff here.</p></html>", "title": "Demo Time!"}
]_.title
Demo Time!

Note a trick applied here: for variable stuff we don't really care about, you can just use "{}" as the placeholder. The last of these will still be stored in the result map (with an empty string for a key), but you can easily ignore it.

Chatbots

Both match and fill make it dramatically easier to create chatbots. A simple but effective chatbot can be just a list of patterns to try and match against, and a list of responses. You can even add the matched responses to a persistent dictionary (called memory in the example below), and then reference that data in later responses.

Here's a very simple example to get you started.

import "stringUtil"
import "listUtil"
memory = {"name":"User"}

while true
	inp = input(">")
	if inp.match("hello{}") or inp.match("hi {}") or inp=="hi" then
		print "Hello, {name}".fill(memory)
		continue
	end if
	
	m = inp.match("call me {name}")
	if not m then m = inp.match("my name is {name}")
	if m then
		memory = memory + m
		print "OK, I'll call you {name} from now on".fill(memory)
		continue
	end if
	
	m = inp.match("what{}favorite {item}")
	if m then
		print "I don't really have a favorite {item}".fill(m)
		continue
	end if
	
	print "that's interesting"
end while
And here's an example run:
Hello, User
>my name is Joe
OK, I'll call you Joe from now on
>what is your favorite food
I don't really have a favorite food
>what is your favorite sports team
I don't really have a favorite sports team
>well you seem rather dull
that's interesting

Why not start with this program, and see how smart you can make it! Add lots of patterns to anticipate anything the user might say. You might want to add some utilities to standardize punctuation and capitalization, and perhaps make a matchAny function that lets you specify a list of alternate patterns to match against.

Localization

Another common use for fill, in particular, is in localizing your software to different languages. In this case you'll probably have a tab-delimited file with one row for each string your program needs to display, and a column for each language you support. Those localized strings can then include placeholders for specific data that needs to be filled in at runtime. This is important since where that data goes may vary with the language; you can't assume it will always be at the end, for example.

To illustrate, here's a simple example that expresses the same data in English and French.

]en = "You have ${money} left."
]fr = "Il vous reste {money} $."
]money = 42
]en.fill(locals)
You have $42 left.
]fr.fill(locals)
Il vous reste 42 $.

Of course, using fill makes your code neater and cleaner than combining strings with + even if you aren't supporting multiple languages. But if you are, it becomes indispensible.

Conclusion

This post has covered the new fill and match methods added to string by stringUtils. I hope you now see why these should be valuable additions to your toolbox.