App is written in more or less generic but relatively modern C++ (and itself of course). In particular it utilizes namespaces, RTTI, (simple) templates, and some STL containers (but not exceptions). It should be easy enough to port to just about any platform that supports a relatively modern C++ compiler.
The app code now runs under both MS VC++ 5.0 and g++ 2.91.66. I have isolated most (if not all) known platform specific constructs by using #ifdef WIN32 and #ifdef GCC constructs, and by using files app_platform.h and applib_platform.h in the app & applib directories that localize platform specific includes etc. Note that GCC is presently defined if WIN32 is not (see app_platform.h). There are also a number of #ifdef GCC_BUG constructs that hopefully will go away once g++ iostream support sufficiently improves (GCC_BUG is defined if GCC is).
The free (beta) cygnus cygwin software works pretty well for providing certain Unix-like utilities for Windows; see http://sourceware.cygnus.com.
There are a number of directories under the top-level alpha directory that serve to isolate the major software components. The app directory contains the source for most of app itself; the directories app_parser and app_scanner contain the source for the parser and scanner components, organized as static libraries linked with the main code in the app directory.
There are a couple of subdirectories under app that I use for testing purposes. The test subdirectory is for sanity checking stuff. The prev subdirectory is used for fixpoint testing; typing 'make fix' will check that app is still able to regenerate itself. Changing the code enough for fixpoint testing suggests that you best have a stable previous version stashed away somewhere first. See the makefile for details.
The applib directory contains the runtime library code. Note that app depends on the library (for self-defining reasons). The skel directory contains the code for a small standalone program ("skel") used to generate a compilable header file (skeletons.h) containing class translation skeletons from the associated input file skeletons.txt. The docins directory also contains a small standalone program ("docins") for generating .html and .txt files in the doc directory from a set of specially marked-up .txt files.
I use flex and bison for the app scanner and parser components. You will need to use something similar if you change either. All major components live in their own files including most classes. Most symbols live in a namespace, of which there are several.
App defines a set of framework classes that coordinates post-initialization activity by means of singleton objects established at initialization; see also the framework.* and driver.* files. The framework establishes four major code categories: scanner, screener, parser, and interpreter.
One of the underlying code philosophies is that it should be easy to use standard C scanners and parsers (e.g. flex & bison) even though the asts the parser generates are instances of alpha types. See the alpha.h and c_*.* files for details about interfacing reference counted ast objects with standard C code (it's a bit tricky but not too bad).
The general post-initialization control flow can be summarized as follows.
(parser calls screener calls scanner for each token)
every time the parser recognizes an alpha construct it calls interpret
interpret dispatches Alpha_Type_Decl or Alpha_Nested operator ()..
Alpha_Type_Decl operator () handles alpha type declarations
Alpha_Nested operator () dispatches Match or Option operator ()..
Match operator () handles match statements
Option operator () handles embedded $option constructs
App provides a factory class 'Make' that can make it easy to customize the code without having to modify in many cases or recompile in some cases the original code. Most internal classes (except those involving self-defining alpha types) have associated construction functions as virtual members of the Make class, and which app calls upon to create related objects.
To customize such an internal class, specialize by derivation both the internal class to be customized, and the Make class by overriding the relevant virtual construction function so as to create an object of the specialized internal class; then arrange to define the singleton Make* factory::make object as an instance of the specialized Make class prior to app initialization - i.e. prior to main calling Driver::app (see also main.cpp).
It should only be necessary to modify the existing main.cpp file to establish the new factory class, then recompile the app code and relink with the custom code which presumably lives in its own directory, perhaps as a static library. Once the app code has been recompiled with the new main.cpp, it should only be necessary to relink it thereafter, provided that changes to the existing app code files are confined to main.cpp.
The scanner and parser components involve generated C code, so to customize that code it is in general necessary to build new static libraries for them, in addition to specializing the Make class, and possibly the framework classes A_Scanner and A_Parser, if needed to handle the new scanner or parser; the existing framework specializing classes Scanner and Parser may also be specialized. Some of the code in the app directory includes headers from the app_parser and app_scanner static library directories, so place the new versions in the same directories after having saved away the originals.
One thing you really have to watch out for is changing the code in such a way that app is no longer able to regenerate itself. It is not so bad if you can use a previous stable version to patch things up but that is not always the case (of course at least theoretically you can always completely undo whatever changes fowled things up in the first place, but I'm talking about dealing with the changes rather than undoing them).
If the nature of the changes is such that you are tempted to back-propagate the changes into a stable previous version, you almost certainly shouldn't. One situation where this might crop up is when you change a skeleton or the core alpha.* library code in such a way that invalidates a skeleton. Rather than going back to a previous version and trying to patch up its skeleton or alpha.* files (which might entail going to a previous-previous version... etc. etc.), try the --skeletons=f option. It lets you dynamically load skeletons in a way that overrides the default skeletons as defined by the compiled skeletons.h file.
In any event, and as a general rule, it is best to introduce those kinds of changes small portions at a time if at all possible.
App is not done. If you have an idea & want to do something about it, please do. Also, please let me know; I am willing to consider folding such code into my code base if you like. You might also want to check the app home page before starting a new project, as I intend to maintain an up-to-date status report there regarding such matters. Here then are some project ideas.
Implement an efficient allocator - see the alpha.* files.
Provide object input not just output - see the beta.h file. Input should be consistent with output.
Turn the beta.h List<A> type into an STL compatible container class. Such a class could also use as a basis the current Extending_List<A> class in beta.h as it already provides much of the required functionality.
Handle generic objects, in a manner akin to how void* pointers can be used to
reduce extraneous template expansions. I'm not convinced though that alpha
types are all that problematic in this way - who really cares if there are 23
versions of List<A>::length() anyway? (In my Pascal days I once thought that's
the way you had to do it). But supposing it's worth it, one way to do generics
would be to introduce an intermediary class T_ between the handle/body classes
T and T__, as illustrated below (a $generic app keyword also seems needed).
class GPair__ : public Top__ {
public:
GPair__(Top a, Top b) : a_(a), b_(b) {}
protected:
Top a_;
Top b_;
};
template <class A, class B>
class GPair_ : public GPair__ {
public:
GPair_(A a, B b) : GPair__(static_cast<Top>(a), static_cast<Top>(b)) {}
A a() {return A(a_());}
B b() {return B(b_());}
};
template <class A, class B>
class GPair : public Top {
public:
GPair() : Top() {}
GPair(A a, B b) : Top(new GPair_<A, B>(a, b), nocopy) {}
GPair_<A, B> *operator->() const {
return static_cast<GPair_<A, B> *>(handle);
}
GPair_<A, B> &operator*() const {
return static_cast<GPair_<A, B> &>(*handle);
}
static GPair<A, B> nil() {return GPair<A, B>(0, nocopy);}
};