There are two basic styles of problem solving in programming:
Some programmers are a bit more clever and ``teach'' the computer how to solve the class of problem they are facing and then ask the computer to solve the particular problem they are facing.
As far-fetched as the latter technique may be (conjuring up images of a HAL 9000) it is certainly nothing new to the world of programming and has been used many times over.
Naturally, programmers that use the latter technique tend to write more readable, maintainable and efficient code. Because the solution to the problem is stated in its most natural terms rather than encoded in programming languge.
Some programming languages (notably Lisp and FORTH) allow the programmer
to extend the syntax of the language so that ``the computer can do the
work.'' Lisp, for example, has a powerful macro facility that takes
advantage of the fact that Lisp code is also Lisp data. While
ANSI C has a macro processor, it is rather limited in
what it can do. It is almost impossible to create a set of macros in
C that support an object-oriented programming style
(actually, it is possible, but the result sure isn't pretty).
AMC attempts to provide a very intelligent macro processor that can be ``bolted on'' to any language to be used as a back end. AMC is then programed with the syntax that it is to process. AMC takes the syntax description and a set of source modules as input and produces output files that are run through other programs (such as a C compiler) to produce the desired output.
AMC originally started life as a preprocessor to C to
add a more dynamic form of object-orientation than languages such as
C++
provided. However, in my zeal to make AMC a general tool I made the
C
source code to AMC easily extensible by writing new routines and plugging
them into a table of ``syntaxes.'' However, I soon found this too
inconvenient to continually implement new language features (in
C) and recompile AMC (not a speedy task on my
hardware).
To solve this annoyance, I replaced all of the object-oriented preprocessor code in AMC with something called CGL. CGL (short for Code Generation Language) is a little programming language oriented towards generating code from specifications.
It is now possible to impelment any form of expression by defining how
to code the final result in a traditional language (such as
C) using the CGL programming language.
Furthermore, AMC also enforces a ridig module structure. This is to
assist with code reuse and to make large projects easier to manage.
Something that C header files are not exactly intuitive about.
While AMC follows POSIX.1 as close as possible, most operating systems do
not. Hence there are several places in the code where bugs must be coded
around. AMC was designed to be portable across platforms from day one.
There is an abstraction layer that presents an idealized view of the
target platform to the rest of AMC. This abstraction layer is the
subsystem called system. If you are porting AMC to a new platform,
any system peculiarities should be relegated to the source files
in system and nowhere else.
Make a directory in the file system you would like AMC to reside.
You don't have to call it amc although this example will assume
/store/amc-vX.Y is the directory for AMC.
The archive is a gzipped UNIX tar file and can be decompressed as follows:
% cd /store
% cp ~/amc-vX.Y.tar.gz .
% gzip -d amc-vX.Y.tar.gz
% tar xvf amc-vX.Y.tar
% rm amc-vX.Y.tar
The archive extracts its self into a directory that has a specific
version stamp on it. So, after the above is complete, you should have
a directory with a name like amc-vX.Y in
/store.
To build AMC, make sure you are in the src directory off the root
of the archive and type the following command:
% ./build
If this is the first time you are building AMC, the script will
prompt you for the answers to various questions. The build script
will also provide defaults that are reasonably safe. So, if you are
unsure go with the defaults.
The most important of all settings is the ``AMC Build directory.''
This parameter must be set correctly or AMC will not compile well
at all. In our example case, this parameter should be
/store/amc-vX.Y and nothing more (no src).
If the build script guesses wrong, you should enter full path of the
directory that the AMC archive creates first.
When you have completed all the questions, build will automatically
begin the compilation process. On subsequent builds, the build script
will not ask the questions. This may present a problem if you are
trying to compile AMC for several architectures. To clear out your
previous settings and have the build script prompt you again, you
should remove the file defaults under the src directory.
In our example, this would be
/store/amc-vX.Y/src/defaults.
As it builds, build will tell you which subsystem it is working on. When the build is complete, it lists the binary file it has made.
Several examples are provided in the examples directory off
the root of the AMC directory.
To make sure everything is working and installed properly, we will try
and compile the simplest project with AMC. This project is called
two_modules, and it is a test of AMC compiling one module
that uses another.
Assuming the amc binary is in your path you can go to the
two_modules
directory and you should get the following:
% amc project
amc - Module Translator Vx.yy [Dec 9 1998] on Linux
Copyright (C) 1997,1998 Mark Grosberg.
All Rights Reserved.
Executing: gcc ${CFLAGS} /store/amc/examples/two_modules...
Executing: gcc ${CFLAGS} /store/amc/examples/two_modules...
Executing: gcc -s -o /store/amc/examples/two_modules/m...
Completed compilation for main_module in def.
If everything went okay you should see a file in the two_modules
directory that looks like this:
% ls -l main_module
-rwxr-xr-x 1 myg users 3460 Dec 9 23:32 main_module
If you don't see the file or you got errors during the execution of AMC
then please double-check your configuration and make sure that
gcc is in your path (or else you have to modify
the standard project file scripts).
What just happend was that you invoked AMC on a project file.
The default action for AMC is to build the project.
Because you have never built this before, AMC processed the source code
and issued three commands to the underlaying operating system: Compile
two C source modules (the output of AMC processing a source
module) and link the object files together.
Of course AMC can do a lot more than work as a better UNIX
make(1), but this is how the example two_modules
uses AMC. In any event, AMC always deals with a project file.
The project file acts as a configuration file to AMC, telling it everything
it needs to know to compile your code.
If you look at the example project file (named project)
in the two_modules
directory you can see how easy it was to write:
set PROJECT_DIR=@("AMCHOME") "/examples/two_modules";
set PROJECT_ROOT="main_module";
include(@("AMCHOME") "/std/simple_c_project");
This project file isn't doing very much. It sets two environment variables and includes another file (a standard - included with AMC - project template). The included file is what does most of the work.
The @("string-literal")
construction is fetching an environment variable.
The environment variable AMCHOME
is set to the directory AMC is being run from (minus the
bin
directory).
For example, say the binary of AMC you are running is
/store/amc-v3.4.1/bin/amc, then
AMCHOME will be set to
/store/amc-v3.4.1.
This way you don't have to compile-in the location of AMC (I hate programs that require this).
The environment variable PROJECT_ROOT is the first
module in the tree to compile.
AMC does not require that a developer compute depedencies
like most tools. Instead, each module explicitly names the
modules that it uses (well, it actually uses their
interfaces).
Given this information and a ``starting point''
AMC can walk the module dependencies of your program and build only the
necessary modules.
To see the actual source modules that comprise the program, go
into the source directory under
two_modules (in the examples directory).
The string source
is not hard-coded into AMC, rather, it is implied by the use of the
simple_c_project
standard template. In fact, very little is actually hard-coded in AMC.
This is the reason why the standard templates (like simple_c_project
are so important: It is too tedious to set all these parameters for
every project.
In the source directory you will find two files:
auxiliary_module.dmain_module.dAMC uses the concept of ``one file is one module.''
As you can see, AMC source modules are UNIX text files
having an extension of .d.
These two modules comprise a real, running program. It is a utility that runs a command over its arguments. But the purpose of this program is not important, rather it is important that you see how modules were used in the construction of the program.
In reality, the module structure for this program is rather arbitrary.
auxiliary_module
contains a command line checking routine and
main_module
contains the remainder of the program.
The purpose of the module structure is to illustrate how AMC works, we
aren't at the point yet where we worry about good module structure
versus bad module structure.
The source code for auxiliary_module
looks like this:
module type c;
interface <-
shared int Argc;
shared char **Argv;
void usage(void);
end;
implementation <-
#include <stdio.h>
void usage(void)
{
/* actual source code here */;
}
end;
To those who have ever used the UCSD p-System, the keywords
interface and
implementation
should stick out like a soar thumb. The interface keyword begins
a block (terminated with the end keyword) that contains a
bunch of public definitions.
The implementation keyword contains the actual module-specific
elements, such as function bodies and variable declarations
(also terminated with the end keyword).
Every module that AMC compiles must
begin with the keyword module (excluding comments and whitespace).
Notice that we define the type of this module as c.
Modules can have different types (with different syntax or even
compiled by a different compiler). The simple_c_project
template only defines the module type c for standard ANSI-C
modules with some the default AMC provided extensions enabled.
The interface to this module defines two global variables
(with the shared keyword) and one function, usage that
takes no parameters and returns no value. The shared keyword
is not really a keyword. It is a macro that AMC defines to the
appropriate value such that global variables do not have to be
declared twice (once as an extern and once to declare the
space) as they normally are in C.
The source code to main_module looks like this:
module type c uses (auxiliary_module);
implementation <-
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
/* Actual source code goes here. */
}
end;
The module keyword of this module also includes a uses clause.
This clause (which can have multiple modules with various options
described later) names each module that this module depends on.
In this case, main_module depends on auxiliary_module's
interface.
Now it should be apparent how AMC traverses the module dependency tree and compiles the necessary modules. AMC decides to make a file using the time/date stamps of a file. So make sure the clock on your computer is set correctly or else it might get confused.