MyDef is a self-bootstrapping meta-programming system. Its source is written in
its own .def format and compiles to Perl. The compiler transforms .def files
into output in any target language (Perl, C, Python, etc.) through a pluggable
module system.
The compilation pipeline has four stages:
.def file
|
v
parseutil -- parse indentation-based syntax into $def structure
|
v
compileutil -- expand macros/subcodes, drive output module callbacks
|
v
output_* -- language-specific parsecode (syntax sugar -> target code)
|
v
dumpout -- flatten nested blocks, resolve indentation, emit text
Reads .def files and builds an internal data structure ($def) containing:
pages-- hash of page definitionspagelist-- ordered list of page namescodes-- subcodes and fncodes (merged at end)macros-- compile-time macro definitions
Key responsibilities:
- Indentation tracking (converts leading whitespace to
SOURCE_INDENT/SOURCE_DEDENTmarkers) include:directive processing with search path (@path,$MYDEFLIB)page:block parsing (page-level attributes liketype,output_dir,package)macros:block parsingsubcode:/fncode:block parsing (with parameters, type tags)template:expansion- Comment filtering (
#lines,/* */blocks) - Multi-file import with include ordering (main file first, then includes, then standard includes)
The compiler core. Drives the output module through a five-callback interface and manages macro expansion and subcode invocation.
Key responsibilities:
- Macro expansion --
$(name)lookups through a$deflistscope chain ([def_root, macros, page]plus dynamically pushed scopes) - Compile-time directives --
$(if:...),$(set:...),$(for:...),$(setmacro:...),$(export:...) - Subcode invocation --
$call name, argsresolves and inlines subcode bodies, withBLOCKinsertion for caller-provided content - Named blocks --
DUMP_STUB namefor deferred insertion points - Output block management -- multiple output streams via
fetch_output/set_output _autoloadsubcodes -- run once per page beforemain- Frame support --
page._frameorbasic_framewraps the main code - Interface push/pop -- allows temporary switching of output modules (e.g. for embedded languages)
Each output module provides five callbacks registered via get_interface():
| Callback | Purpose |
|---|---|
init_page |
Per-page initialization, set file extension |
parsecode |
Line-by-line translation of MyDef syntax |
set_output |
Switch the current output stream |
modeswitch |
Handle mode changes (e.g. HTML vs JS vs PHP) |
dumpout |
Final output pass, add language boilerplate |
output.def provides two base templates:
output_main-- Full base implementation. Subcode hooks (@on_init_page,@parsecode,@dumpout, etc.) let modules inject behavior.inherit(M)-- Inheritance pattern. Delegates to a parent module and intercepts callbacks via@on_*hooks. Used by modules likeoutput_www(inherits fromoutput_perl).
Common parsecode features shared across modules (in parsecode_common):
DUMP_STUBpassthrough$pluginregistration and dispatch$evalfor runtime code evaluationCALLBACKhandlingDEBUGmode toggling
Minimal module. Sets extension to txt. Its parsecode delegates entirely to
parsecode_common, which by default just pushes lines to the output buffer.
Rich Perl code generation with syntax sugar:
$if/$elif/$elsewith regex capture (-> $var)$while,$for,$foreach(within, hash iteration, zip, index variants)$global,$my-- variable declarations (globals becomeour)$sub name(params)-- subroutine definitions$print/$die/$warn-- with color support ($red{...},$green{...})$use-- module imports$dump-- debug printing (auto-importsData::Dumperfor hash dumps)fncode:-- named functions with parameter unpacking- Auto-semicolons -- appends
;to lines that don't end with,,(,[,{, or; break/continue-- translated tolast/next- Package and
#!preamble generation $sumcodefor tensor-style summation loops
Final pass that converts the internal output list into indented text lines. Processes directives:
INDENT/DEDENT-- increment/decrement indentation (4 spaces per level)PUSHDENT/POPDENT-- save/restore indentation levelDUMP_STUB name-- inline a named block (deferred insertion)INSERT_STUB[sep] name-- insert into a{STUB}placeholderINCLUDE_FILE path-- inline a file verbatimINCLUDE_BLOCK name-- inline a named block from the dump contextBLOCK_N-- inline a numbered output blockNEWLINE/NEWLINE?-- explicit/conditional blank lines<-|-- left-aligned output (no indentation, e.g. C preprocessor directives)SOURCE_INDENT/SOURCE_DEDENT-- from the parser, also adjusts indentation\xNNescape processing
A page is the top-level compilation unit. Each page: block becomes one output
file. Page attributes:
page: name
type: pl # file extension
output_dir: lib # output directory
module: perl # which output module to use
package: Foo::Bar # language-specific (Perl package name)
Multiple pages can exist in one .def file. Pages can reference a _frame
subcode that wraps the main content.
Reusable code fragments, the primary abstraction mechanism:
subcode: greet(name)
print "Hello, $(name)\n"
- Invoked via
$call greet, World - Support parameters (positional, expanded as macros)
BLOCKkeyword -- replaced by caller-provided indented content (&call)subcode::(double colon) -- append to existing subcodesubcode:@-- marks extension points ($call @hookis a no-op if undefined)- Scope: page-local subcodes override def-level subcodes
Like subcodes but generate actual functions in the target language:
fncode: add($a, $b)
return $a + $b
In Perl output, this becomes sub add { my ($a, $b) = @_; ... }.
Compile-time text substitution via $(name):
macros:
greeting: Hello
$print $(greeting), World!
Compile-time directives:
$(if:cond)/$(elif:...)/$(else)/$(endif)-- conditional compilation$(set:name=value)-- set macro in current scope$(setmacro:name=value)-- set macro with re-expansion$(for:a,b,c)-- compile-time iteration$(export:name=value)-- propagate macro to outer scope
Language backends registered in modules.def. The module list is built at
compile time using $(setmacro:...):
macros:
module_list: general, perl
subcode: _autoload
$map add_module, c, sh, xs, cpp, java, go, ...
Each module name maps to MyDef::output_NAME and is loaded on demand via
require. The check_module function in mydef.def dispatches to the correct
module using a compile-time $map over $(module_list).
All tool scripts are themselves .def files that compile to Perl scripts
(placed in script/).
Main compiler CLI:
mydef_page source.def
Reads config file for defaults, parses the .def file, and generates output
for each page. Supports -m (module override), -o (output dir), -f (find
subcode), -dump (dump structure), -pipe (stdin/stdout mode).
Compile-and-execute:
mydef_run source.def
Compiles the .def file then runs the result. Dispatches by output file type:
- Compiled languages (C, Fortran, Rust, etc.) -- compile then execute
- Scripted languages (Perl, Python, etc.) -- run with interpreter
- Supports
/* expect: ... */blocks for inline test assertions
Module guessing: scans the source for hints (e.g. module: directive,
include: perl/, or Perl-like syntax like my $var).
Build system generator:
mydef_make
Scans the current directory for .def files, reads their page definitions and
include dependencies, and generates a Makefile. Features:
- Recursive subdirectory processing
- Dependency tracking across includes
- Groups pages by output directory into Makefile variables
- Module-specific Makefile generation (e.g. C compilation rules)
- Copylist support for static assets
Test runner. Reads a TESTS file listing .def files and runs each via
mydef_run. Reports pass/fail counts.
Installs compiled outputs to system directories:
*.def->$MYDEFLIB*.pm->$PERL5LIB- Scripts ->
$PATH(or$MYDEFBIN)
Pulls latest source from git, rebuilds, and reinstalls MyDef and all
output_* extension modules.
Utility functions used throughout:
proper_split-- comma-split respecting balanced brackets and quotesexpand_macro-- recursive$(...)expansion with nested parenthesis handlingget_tlist/get_range-- range expansion (0..9,a..z,0x0..0xf)for_list_expand-- pattern expansion with$1/*replacement,and/mulcombinators
Extension API for user perlcode: blocks to interact with compiler internals:
grab_codelist-- retrieve the last grabbed code blockinject_sub-- programmatically define new subcodesrun_src-- compile and run a source arraygrab_ogdl-- parse structured data (OGDL-like key-value format)
See the pipeline section above.
MyDef compiles itself. The .def source files use MyDef syntax ($if,
$foreach, $call, $(macro), subcodes) to generate the Perl .pm modules
that implement the MyDef compiler. The bootstrap process:
bootstrap.shuses a pre-compiled copy (inmydef_boot/) to compile the.defsources- The compiled output replaces the running compiler
- The new compiler can then recompile itself
The macros_* directories contain the reusable subcode libraries that implement
the compiler's own features -- parsing, macro expansion, compilation, and output
generation.