Modularity in Fexl

Let's say for example you're writing some code related to the old Flintstones cartoon:

main.fxl:

\fred==(say "I am Fred.")
\wilma==(say "I am Wilma.")
\barney==(say "I am Barney.")
\betty==(say "I am Betty.")

fred
wilma
barney
betty

(You'll note that I'm using == instead of = for those definitions because I don't want them to evaluate immediately at the point of definition.)

Now you'd like to move those function definitions into a separate file called "flintstones.fxl". Here's what you do. Cut the definitions out of the main file and paste them into the new "flintstones.fxl" file. Then add a series of "define" calls which export the definitions into the standard context (environment), so it ends up looking like this:

flintstones.fxl:

\fred==(say "I am Fred.")
\wilma==(say "I am Wilma.")
\barney==(say "I am Barney.")
\betty==(say "I am Betty.")

define "fred" fred;
define "wilma" wilma;
define "barney" barney;
define "betty" betty;
standard

Now edit your main file so it looks like this:

main.fxl:

evaluate
(
use "flintstones.fxl";
standard
) \;

fred
wilma
barney
betty

If you create an additional library "lib.fxl" you can add it like this:

main.fxl:

evaluate
(
use "flintstones.fxl";
use "lib.fxl";
standard
) \;

fred
wilma
barney
betty

In that case the definitions in lib.fxl will be visible inside flintstones.fxl.

Now let's say you wanted flintstones.fxl to import lib.fxl automatically, so all you had to say was this:

main.fxl:

evaluate
(
use "flintstones.fxl";
standard
) \;

fred
wilma
barney
betty

In that case you would edit flintstones.fxl as follows:

flintstones.fxl:

evaluate
(
use "lib.fxl";
standard
) \;

\fred==(say "I am Fred.")
\wilma==(say "I am Wilma.")
\barney==(say "I am Barney.")
\betty==(say "I am Betty.")

define "fred" fred;
define "wilma" wilma;
define "barney" barney;
define "betty" betty;
standard

You can even test out modularity within the main file itself. For example, let's say you want to define bam_bam and dino, but only make dino visible to your main code:

main.fxl:

\bam_bam==(say "I am Bam-Bam.")
\dino==(say "I am Dino.")

evaluate
(
define "dino" dino;
use "flintstones.fxl";
standard
) \;

fred
wilma
barney
betty
dino

In that case dino is visible in your main code, but is not visible inside flintstones.fxl because you put the define above the use.

Creating a custom environment which appears built-in

It is also possible to create custom environments that don't have to be included explicitly. This would allow you to write the main file like this, as if the entire Flintstones environment was built into the language:

main.fxl:

fred
wilma
barney
betty
dino

To do that you would need some higher-level script, let's call it "run.fxl", that sets up the environment and calls main.fxl:

run.fxl:

\bam_bam==(say "I am Bam-Bam.")
\dino==(say "I am Dino.")

evaluate
(
define "dino" dino;
use "flintstones.fxl";
standard
) \;

use "main.fxl";
standard

To make it possible to run files other than main.fxl, you can look at the command line arguments (argv), or at the HTTP parameters in the case of a web application, and use any file designated by the caller. For example:

run.fxl:

\bam_bam==(say "I am Bam-Bam.")
\dino==(say "I am Dino.")

evaluate
(
define "dino" dino;
use "flintstones.fxl";
standard
) \;

\script=(argv 2)
use script;
standard

I use this technique extensively in web servers, where I have a plethora of files to serve different types of requests, but I don't want to have to repeat the evaluate context calls in every single file. In that way I establish one common environment shared by all the files.

If I need to make an exception, that's easy to do. For example I might want to evaluate a particular file in a highly restricted context, to avoid possible harm from a user-specified script, or from any script that does not explicitly require a particular dangerous power.

The Basis of Modularity

The fundamental basis for modularity in Fexl is the evaluate function. It takes two arguments:

evaluate context form

The context is a function which maps a symbol (string) to its corresponding definition, or void if the symbol is undefined.

The form is an "unresolved form", which is a piece of Fexl code which has been successfully parsed, but whose symbols have not yet been resolved with specific definitions. That is the purpose of the "\;" that you see in the examples above. The "\;" treats everything following it in its scope as an unresolved form. The evaluate function finds all the symbols in the form and uses the context to supply a definition for each one. If any symbols are not defined, it reports each occurrence to stderr and dies.

The symbol standard always refers to the current context used to resolve the form in which it appears. In other words, it gives you a "handle" to the context used to resolve the very piece of code you're looking at. That is how contexts are extended by the calls to define above.