DEV Community

JoeStrout
JoeStrout

Posted on

if locals == globals

In MiniScript, as in some other languages, it is common to write a file that's intended to be imported as a module in some larger program — but sometimes, to be loaded and run by itself. Typically in the latter case, the point is to demonstrate or unit-test the functionality of that module.

For that to work, your code has to know whether it's being imported, or run in the main program. Today, we're going to see how that's done.

if locals == globals then...

The simple trick is to check whether locals is equal to globals. This is only true for code running in the global scope, that is, in the main program. Import modules run in their own scope, so for them, locals and globals are different.

So a common pattern you'll find at the bottom of many import modules looks like this:

if locals == globals then
   // do some demo or unit test
end if
Enter fullscreen mode Exit fullscreen mode

...run a demo!

A good example of this is /sys/lib/chars, which defines names for all the various special characters in the Mini Micro font. At the bottom of that file, you'll find:

if locals == globals then
    text.clear
    text.row = 25
    print "Special characters:"
    print
    printAlign = function(label, text)
        print " " * (30 - label.len) + label + ": " + text
    end function
    printAlign "left or leftArrow", leftArrow
    printAlign "up or upArrow", upArrow
    // (...etc...)
end if
Enter fullscreen mode Exit fullscreen mode

Screen shot of /sys/lib/chars

...or unit tests!

Many of the other modules in /sys/lib have a runUnitTests function, and the bottom of those generally looks like this:

if globals == locals then runUnitTests
Enter fullscreen mode Exit fullscreen mode

You'll find this in /sys/lib/mathUtil, as well as listUtil, stringUtil, and many others. (Note that we're comparing in the other order here — globals == locals rather than locals == globals — which makes no difference at all.)

This trick isn't just for general-purpose libraries, either. I used it extensively in making Kip and the Caves of Lava, which consists of over a dozen MiniScript files. Most of these can be run on their own. When run in this way, each module puts its own code through its paces, so I could code/test/debug on just that file, without having to run the whole game.

When is demo also a library?

Occasionally you'll find a program that was intended primarily as a stand-alone demo, to be run and enjoyed on its own, but which can also be imported like a library module. Sometimes this is for historical reasons: something started as a demo, but it was realized that some of its classes and methods might be useful in other apps, so it was made importable.

This is done in exactly the same way as above: all the visible functionality or main program is moved into an if locals == globals block.

Take /sys/demo/cardFlip, for example. This was originally just a stand-alone demo, but in Mini Micro version 1.2, it was refactored to be importable — a fact we used recently in making Solitaire. That entailed moving everything besides the CardSprite class into a demo function, and then ending with

if locals == globals then demo
Enter fullscreen mode Exit fullscreen mode

So if you just load and run cardFlip, it behaves exactly as it did before; but now you can also choose to import it (after ensuring that /sys/demo is in your env.importPaths), and use the same CardSprite class in your own code.

Another example is /sys/demo/textAdventure. After defining a bunch of classes and methods for parsing commands, representing rooms, doors, and other objects, etc., the main program starts around line 600:

//----------------------------------------------------------------------
// Main program.
if locals == globals then

    origTextColor = text.color
    origBackColor = text.backColor
    normalColor = color.green
    text.color = normalColor
    text.backColor = color.black
    clear
Enter fullscreen mode Exit fullscreen mode

This code goes on to create the specific text adventure The Greedy Gargoyle, which is what you'll play if you run this program. But if you want to make your own text adventure, you can import this code, and none of that main program stuff runs. You can then make use of all those support classes and functions in your own code.

Screen shot of /sys/demo/textAdventure

No suffocating snakes here

You'll find a similar concept in some other languages. In Python, it's done this way:

if __name__ == '__main__':
Enter fullscreen mode Exit fullscreen mode

But this requires that you remember not one but two magic words, __name__ and __main__, which have little purpose anywhere else. The equivalent MiniScript trick uses no magic words; locals and globals are just the standard, ordinary references to local and global variables, and have many uses besides this trick.

So, the next time you see if locals == globals in some MiniScript file, you'll know what it means. And when writing your own code, I hope you'll remember to consider whether it could be both imported and run on its own — and now you know how to make that work.

Top comments (0)