Contents
Intro ↩
METAL is a freeware BASIC IDE by Marin Saric of Galactic Dreams Software, and is quite a capable development tool if you’re into lateral-thinking-to-overcome-the-limitations-of-the-language-development…
However, the language has a few limitations that I feel merely irritate rather than present an interesting challenge to overcome: no support for multiple files, no support for named constants, no requirement that variables be declared/no scoping of variables, no support for parameterised functions, …
So, I’ve developed HEAVY METAL {source code|compiled:Mac OS pre-X} to try to remedy some of those limitations.
HEAVY METAL is a pre-compiler: taking one HEAVY METAL-language file (which may reference other such files) as input, one METAL-language file is output; that output file can then be compiled using METAL.
HEAVY METAL introduces several instructions to try to remedy several of the aforementioned limitations of METAL, such as instructions for declaring and calling parameterised functions, but, as a pre-compiler, is limited in the features it can introduce to METAL; however, with a bit of lateral thinking, even features such as recursive parameterised functions and polymorphic parameterised functions (planned for a future update to HEAVY METAL) can be implemented – so, if you have a suggestion for an instruction or feature, I would be happy to receive it (with the caveat that instructions and features will be implemented in the order that they are useful to me…) Similarly, I would be happy to receive reports of any bugs not included in the list below.
Every instruction introduced by HEAVY METAL begins with a ‘@’ character, and ends with a ‘ ‘ character, eg. “@new_scope·variable;” and “@delete_scope·“.
Due to METAL‘s lack of in-depth language documentation, HEAVY METAL does not try to check for errors in the METAL-language files it produces.
To use HEAVY METAL, copy all of the HEAVY METAL-language files pertaining to your project into the same folder as HEAVY METAL; run HEAVY METAL, type in the name of your project’s primary HEAVY METAL-language file and press the Return key, and that file (and any file it references) will be ‘compiled’, producing a METAL-language file with the same name as that file, and the suffix .hmet.
So far, HEAVY METAL has been tested only with METAL 1.0ß; as updated versions of METAL are released, HEAVY METAL will be updated to cope with any changes to the language (unless of course METAL is updated to remedy those limitations which prompted the development of HEAVY METAL…)
Instructions ↩
@include | Include file ↩
@include <file>;
Inserts the contents of <file>, which undergo the same ‘compilation’ as any other file; @include instructions may be nested, viz., a file can include another file, which can include another file, …
The file @include (name).bas:
"Mark"
The file @include (greeting).bas:
@new_scope n_suffix$;
if £n = 1 then
£n_suffix$ = "st"
else
if £n = 2 then
£n_suffix$ = "nd"
end if
end if
print "Greetings, world! This is the "; £n; £n_suffix$; " time I've greeted you! My name is " + @include @include (name).bas; + "!"
@delete_scope
The file @include.bas:
@new_scope n;
£n = 1
@include @include (greeting).bas;
@++ £n;
@include @include (greeting).bas;
@delete_scope
The result of ‘compiling’ @include.bas:
xmwpiq_n = 1
if xmwpiq_n = 1 then
nsmopg_n_suffix$ = "st"
else
if xmwpiq_n = 2 then
nsmopg_n_suffix$ = "nd"
end if
end if
print "Greetings, world! This is the "; xmwpiq_n; nsmopg_n_suffix$; " time I've greeted you! My name is " + "Mark" + "!"
xmwpiq_n = ( xmwpiq_n + 1 )
if xmwpiq_n = 1 then
ctohvj_n_suffix$ = "st"
else
if xmwpiq_n = 2 then
ctohvj_n_suffix$ = "nd"
end if
end if
print "Greetings, world! This is the "; xmwpiq_n; ctohvj_n_suffix$; " time I've greeted you! My name is " + "Mark" + "!"
@FALSE | False constant ↩
@FALSE
Inserts METAL‘s false constant, 0.
Mark_likes_seals = @FALSE
if Mark_likes_seals then
print "All is right with the world."
else
print "Something is very wrong with the world…"
end if
Result of ‘compilation’:
Mark_likes_seals = 0
if Mark_likes_seals then
print "All is right with the world."
else
print "Something is very wrong with the world…"
end if
@TRUE | True constant ↩
@TRUE
Inserts METAL‘s true constant, -1.
Mark_likes_seals = @TRUE
if Mark_likes_seals then
print "All is right with the world."
else
print "Something is very wrong with the world…"
end if
Result of ‘compilation’:
Mark_likes_seals = -1
if Mark_likes_seals then
print "All is right with the world."
else
print "Something is very wrong with the world…"
end if
@B | Binary constant ↩
@B <binary constant>;
Replaces <binary constant> with the equivalent decimal constant.
print "I was born in the "; @B 111;; "th month."
Result of ‘compilation’:
print "I was born in the "; 7; "th month."
@H | Hexadecimal constant ↩
@H <hexadecimal constant>;
Replaces <hexadecimal constant> with the equivalent decimal constant.
print "I was born in the year "; @H 7BF;; "."
Result of ‘compilation’:
print "I was born in the year "; 1983; "."
@CONSOLE | Console constant ↩
Inserts METAL‘s console constant, 0.
w = 48
h = 48
image = init screen( 0, 0, ( w * 2 ), h )
set screen to image
loadpict "image.pict"
set screen to console
copyrect masked 0, 0, w, h, w, 0, ( w * 2 ), h, 0, 0, w, h, image, image, @CONSOLE
Result of ‘compilation’:
w = 48
h = 48
image = init screen( 0, 0, ( w * 2 ), h )
set screen to image
loadpict "image.pict"
set screen to console
copyrect masked 0, 0, w, h, w, 0, ( w * 2 ), h, 0, 0, w, h, image, image, 0
@new_scope & @delete_scope | Declaring/scoping of variables ↩
@new_scope <variable1>,<variable2>,<…>;
<…>
£<variable>
<…>
@delete_scope
When developing in METAL, it is easy to make a mistake in typing the name of a variable, or to cause a clash with the names of variables in subs (and now parameterised functions), resulting in bugs with hard to track down causes.
To try to remedy these causes of bugs, HEAVY METAL introduces declaring/scoping of variables, at the minor cost of typing an extra character before the name of a scoped variable.
The @new_scope instructions begins a new scope, and is followed by a list of the names of the variables introduced in that scope – a scoped variable can have the same name as a non-scoped variable or a scoped variable introduced in the scope immediately prior without clashing.
To use a scoped variable, simply prefix the name of the variable with a ‘£’ character.
Scoped variable names are not case-sensitive.
The @delete_scope instruction ends a scope.
Scopes are implemented by generating a random, unique prefix for each scope; when a scoped variable is used, it is simply given that prefix.
print "This program will print the numbers 21, 7, 1983, and 1997, in that order; each number will be printed on a new line."
v = 1997
@new_scope v;
£v = 1983
@new_scope v;
£v = 7
@new_scope v;
£v = 21
print £v
@delete_scope
print £v
@delete_scope
print £v
@delete_scope
print v
Result of ‘compilation’:
print "This program will print the numbers 21, 7, 1983, and 1997, in that order; each number will be printed on a new line."
v = 1997
mnjmuu_v = 1983
xdrhri_v = 7
lujggi_v = 21
print lujggi_v
print xdrhri_v
print mnjmuu_v
print v
@begin_func, @end_func, & @func | Declaring/calling functions ↩
@begin_func <function>:<parameter1>,<parameter2>,<…>;
<…>
@end_func
@func <function>:<parameter1>,<parameter2>,<…>;
HEAVY METAL introduces parameterised functions; the @begin_func instruction begins the declaration of a function, and is followed by the name of the function, terminated by a ‘:’ character; that is then followed by a list of the names of the parameters of the function.
The @end_func instruction ends the declaration of the function; note that the function must be explicitly returned from using METAL‘s return instruction.
Parameterised functions are implemented using subs; the names of the parameters are prefixed with the name of the function to avoid clashes with variables elsewhere in the code; the parameters are then assigned the values passed to them, and the sub is called using METAL‘s gosub instruction.
Neither function nor parameter names are case-sensitive.
If in the declaration of a function a parameter is prefixed by a ‘~’ character, the value of that parameter is passed back when returning from the function.
goto MAIN
@begin_func Highest:value1,value2,~highestValue;
if value1 > value2 then
highestValue = value1
else
highestValue = value2
end if
return
@end_func
MAIN:
@func Highest:21,7,highestValue;
print "The highest value is: "; highestValue; "."
Result of ‘compilation’:
goto MAIN
highest:
if highest_value1 > highest_value2 then
highest_highestvalue = highest_value1
else
highest_highestvalue = highest_value2
end if
return
MAIN:
highest_value1=21
highest_value2=7
highest_highestvalue=highestValue
gosub highest
highestValue=highest_highestvalue
print "The highest value is: "; highestValue; "."
@++ | Increment variable ↩
@++ <variable>;
Inserts instructions for incrementing <variable> by 1.
m = 6
@++ m;
print "I was born in the "; m; "th month."
Result of ‘compilation’:
m = 6
m = ( m + 1 )
print "I was born in the "; m; "th month."
@– | Decrement variable ↩
@-- <variable>;
Inserts instructions for decrementing <variable> by 1.
y = 1984
@-- y;
print "I was born in the year "; y; "."
Result of ‘compilation’:
y = 1984
y = ( y - 1 )
print "I was born in the year "; y; "."
@+= | Increase variable by ↩
@+= <variable1>,<variable1, 2, or constant>;
Inserts instructions for increasing <variable1> by <variable1, 2, or constant>.
m = 3.5
@+= m,m;
print "I was born in the "; m; "th month."
Result of ‘compilation’:
m = 3.5
m = ( m + m )
print "I was born in the "; m; "th month."
@-= | Decrease variable by ↩
@-= <variable1>,<variable1, 2, or constant>;
Inserts instructions for decreasing <variable1> by <variable1, 2, or constant>.
y = 1997
z = 14
@-= y,z;
print "I was born in the year "; y; "."
Result of ‘compilation’:
y = 1997
z = 14
y = ( y - z )
print "I was born in the year "; y; "."
@*= | Multiply variable by ↩
@*= <variable1>,<variable1, 2, or constant>;
Inserts instructions for multiplying <variable1> by <variable1, 2, or constant>.
d = 7
@*= d,3;
print "I was born on the "; d; "st of July."
Result of ‘compilation’:
d = 7
d = ( d * 3 )
print "I was born on the "; d; "st of July."
@/= | Divide variable by ↩
@/= <variable1>,<variable1, 2, or constant>;
Inserts instructions for dividing <variable1> by <variable1, 2, or constant>.
m = 21
@/= m,3;
print "I was born in the "; m; "th month."
Result of ‘compilation’:
m = 21
m = ( m / 3 )
print "I was born in the "; m; "th month."
@&= | Logically AND variable ↩
@&= <variable1>,<variable2 or constant>;
Inserts instructions for performing a logical AND of <variable1> and <variable2 or constant>, assigning the result to <variable1>.
v = @B 111111;
print "I'm pretty sure I wasn't born on the "; v; "rd of July…"
@&= v,@B 010101;;
print "I was born on the "; v; "st of July."
Result of ‘compilation’:
v = 63
print "I'm pretty sure I wasn't born on the "; v; "rd of July…"
v = ( v and 21 )
print "I was born on the "; v; "st of July."
@|= | Logically OR variable ↩
@|= <variable1>,<variable2 or constant>;
Inserts instructions for performing a logical OR of <variable1> and <variable2 or constant>, assigning the result to <variable1>.
v = @B 010000;
print "I'm pretty sure I wasn't born on the "; v; "th of July…"
@|= v,@B 000101;;
print "I was born on the "; v; "st of July."
Result of ‘compilation’:
v = 16
print "I'm pretty sure I wasn't born on the "; v; "th of July…"
v = ( v or 5 )
print "I was born on the "; v; "st of July."
@!= | Logically NOT variable ↩
@!= <variable>;
Inserts instructions for performing a logical NOT of <variable>, assigning the result to <variable>.
v = @B 101010;
print "I'm pretty sure I wasn't born on the "; ( v and @B 111111; ); "nd of July…"
@!= v;
print "I was born on the "; ( v and @B 111111; ); "st of July."
Result of ‘compilation’:
v = 42
print "I'm pretty sure I wasn't born on the "; ( v and 63 ); "nd of July…"
v = ( not v )
print "I was born on the "; ( v and 63 ); "st of July."
@” | Quotation mark ↩
@"
Inserts instructions for inserting a quotation mark character.
print "Mark " + @" + "sealfin" + @" + " Bishop"
Result of ‘compilation’:
print "Mark " + chr$( 34 )+ "sealfin" + chr$( 34 )+ " Bishop"
Bug ↩
-
Comments are not respected, viz., HEAVY METAL instructions in comments will still be ‘compiled’; for many instructions, this is not a problem, but not for all, as the following two programs demonstrate.
Program #1:
rem My_name_is_Mark = @TRUE
Result of ‘compilation’:
rem My_name_is_Mark = -1
Program #2:
goto MAIN
@begin_func Highest:value1,value2;
@new_scope highest;
if value1 > value2 then
£highest = value1
else
£highest = value2
end if
print "Out of "; value1; " and "; value2; ", "; £highest; " is the highest value."
return
@delete_scope
@end_func
MAIN:
rem @func Highest:21,7;
Result of ‘compilation’:
goto MAIN
highest:
if highest_value1 > highest_value2 then
kydcwk_highest = highest_value1
else
kydcwk_highest = highest_value2
end if
print "Out of "; highest_value1; " and "; highest_value2; ", "; kydcwk_highest; " is the highest value."
return
MAIN:
rem highest_value1=21
highest_value2=7
gosub highest
-
If a function calls another function, the first function’s parameters cannot be passed straight to the second, as the parameters will not be properly prefixed with the name if the first function during ‘compilation’; for now this bug can be circumvented by assigning the first function’s parameters to intermediate variables, and passing those intermediate variables to the second function; the following two programs demonstrate the bug, and circumventing the bug.
Program #1:
goto MAIN
@begin_func function1:parameter1$;
print "parameter1$: " + @" + parameter1$ + @"
return
@end_func
@begin_func function2:parameter2$;
@new_scope s$,i,c$;
@func function1:parameter2$;
£s$ = ""
for £i = 1 to len( parameter2$ )
£c$ = mid$( parameter2$, £i, 1 )
if ( asc( £c$ ) >= asc( "a" )) and ( asc( £c$ ) <= asc( "z" )) then
£c$ = chr$( asc( "A" ) + ( asc( £c$ ) - asc( "a" )))
end if
@+= £s$,£c$;
next £i
print "parameter2$: " + @" + £s$ + @"
return
@delete_scope
@end_func
MAIN:
print "This program should print two strings, the second a capitalised copy of the first."
@new_scope s$;
£s$ = "Cower now brief mortals, for I am Death, 'gainst whom no lock will hold nor fastened portal bar"
@func function2:£s$;
@delete_scope
Result of ‘compilation’:
goto MAIN
function1:
print "parameter1$: " + chr$( 34 )+ function1_parameter1$ + chr$( 34 )
return
function2:
function1_parameter1$=parameter2$
gosub function1
fjofsy_s$ = ""
for fjofsy_i = 1 to len( function2_parameter2$ )
fjofsy_c$ = mid$( function2_parameter2$, fjofsy_i, 1 )
if ( asc( fjofsy_c$ ) >= asc( "a" )) and ( asc( fjofsy_c$ ) <= asc( "z" )) then
fjofsy_c$ = chr$( asc( "A" ) + ( asc( fjofsy_c$ ) - asc( "a" )))
end if
fjofsy_s$ = ( fjofsy_s$ + fjofsy_c$ )
next fjofsy_i
print "parameter2$: " + chr$( 34 )+ fjofsy_s$ + chr$( 34 )
return
MAIN:
print "This program should print two strings, the second a capitalised copy of the first."
olqxut_s$ = "Cower now brief mortals, for I am Death, 'gainst whom no lock will hold nor fastened portal bar"
function2_parameter2$=olqxut_s$
gosub function2
Program #2:
goto MAIN
@begin_func function1:parameter1$;
print "parameter1$: " + @" + parameter1$ + @"
return
@end_func
@begin_func function2:parameter2$;
@new_scope s$,i,c$;
£s$ = parameter2$
@func function1:£s$;
£s$ = ""
for £i = 1 to len( parameter2$ )
£c$ = mid$( parameter2$, £i, 1 )
if ( asc( £c$ ) >= asc( "a" )) and ( asc( £c$ ) <= asc( "z" )) then
£c$ = chr$( asc( "A" ) + ( asc( £c$ ) - asc( "a" )))
end if
@+= £s$,£c$;
next £i
print "parameter2$: " + @" + £s$ + @"
return
@delete_scope
@end_func
MAIN:
print "This program should print two strings, the second a capitalised copy of the first."
@new_scope s$;
£s$ = "Cower now brief mortals, for I am Death, 'gainst whom no lock will hold nor fastened portal bar"
@func function2:£s$;
@delete_scope
Result of ‘compilation’:
goto MAIN
function1:
print "parameter1$: " + chr$( 34 )+ function1_parameter1$ + chr$( 34 )
return
function2:
uduavc_s$ = function2_parameter2$
function1_parameter1$=uduavc_s$
gosub function1
uduavc_s$ = ""
for uduavc_i = 1 to len( function2_parameter2$ )
uduavc_c$ = mid$( function2_parameter2$, uduavc_i, 1 )
if ( asc( uduavc_c$ ) >= asc( "a" )) and ( asc( uduavc_c$ ) <= asc( "z" )) then
uduavc_c$ = chr$( asc( "A" ) + ( asc( uduavc_c$ ) - asc( "a" )))
end if
uduavc_s$ = ( uduavc_s$ + uduavc_c$ )
next uduavc_i
print "parameter2$: " + chr$( 34 )+ uduavc_s$ + chr$( 34 )
return
MAIN:
print "This program should print two strings, the second a capitalised copy of the first."
fxqyrj_s$ = "Cower now brief mortals, for I am Death, 'gainst whom no lock will hold nor fastened portal bar"
function2_parameter2$=fxqyrj_s$
gosub function2
Outro ↩
HEAVY METAL was developed on an Apple Power Macintosh 8100 in C++ compiled with Metrowerks CodeWarrior IDE 2.1 (Discover Programming Edition.)