Bard Syntax: Preprocessor, JS, Aliasing, Modules
Additional syntax changes for new Bard.
-
Type 'Variant' Reintroduced
Variant is a type that can store data of any other type. Its implementation will vary
per target language. -
Streamlined Preprocessor Directives
No square brackets.
Old Bard:
$[include "Actor.bard"]
New Bard:
$include "Actor.bard"
Can use literal strings as defines whose scope is limited to preprocessor directives.
$define "C" true ... local C = 0 # not affected by the 'define' ... $if ("C") ... # this is true
$target expression
is a convenient way to write conditional code.This code:
$target "C" # This code only compiled if "C" is defined. $target "C++" or "Java" # This code only compiled if "C++" or "Java" is defined. $target all # This code is always compiled.
is functionally equivalent to this code:
$if ("C") # This code only compiled if "C" is defined. $endIf $if ("C++" or "Java") # This code only compiled if "C++" or "Java" is defined. $endIf # This code is always compiled.
Conditional compilation directives now support single-line $if/$elseIf/$else.
$if ("C" or "C++") c_code() $elseIf ("Java") java_code() $else generic_code()
$include now operates preemptively.
In other words included files are now parsed before the current file finishes parsing. This allows meta functions to be included from a main file that affect the parsing of all remaining files (see below).
-
JavaScript Meta Functions
The following syntax allows Bard source code to define JavaScript callbacks that will be run as part of the compilation process:
$fn function_name(args) { ... JS code ... }
The name and sometimes the context of the JavaScript function determines its role and the time that it's invoked; you'll have to refer to a reference to find the name and parameter details of each type of meta function you can define as well as what global compiler context you'll be able to refer to. Here are some conceptual examples of the meta functions that Bard will probably support.
These first two can affect the way that files are parsed. In a typical project these might be defined in a utility library that is $include'd from the main file, so they would be installed early on and then they would handle every source file after the main file and the utility file.
$fn process_source( filepath, source ) { // 'source' is the raw string data loaded from the given 'filepath'. // It can be manipulated here before being returned to be tokenized // and parsed. return source; } $fn preprocess( filepath, tokens ) { // 'tokens' is an array of tokens that can be modified before being parsed. return tokens; }
Note that multiple meta functions can be defined for the same callback; each one is then executed in the order it was defined.
Here's a toy example of defining a custom control structure. It adds a new keyword 'sometimes' to the compiler, where
sometimes statements
(orsometimes[EOL]statements[EOL]endSometimes
) causes the given statements to (randomly) execute about half the time. This implementation modelssometimes
as anif
statement, but it would also be possible to produce a custom node type.$fn custom_token_types { return [ {keyword:"sometimes"}, {keyword:"endSometimes",terminal:true} ]; } $fn define_parser_extensions { Parser.define_control_structure( "sometimes", function(t) { var condition = new Cmd.GT( new Cmd.Access(t,"Math","random"), new Cmd.LiteralReal(t,0.5) ); var cmd_if = new Cmd.If( t, condition ); if (this.parse_statements(cmd_if.statements)) { // Returns true after parsing EOL followed by multi-line statements; // returns false if single-line statements were parsed instead. this.must_consume( TokenType.KEYWORD_endSometimes ); } return cmd_if; } ); }
-
Alias Operator
Fat Arrow operator (
=>
) is now the 'alias' operator that aliases a routine or method to produce an alternate parse tree or to generate alternate output code. If used it must be written before the first statement in a method body to effectively switch the compiler to alias definition mode. All statements then become part of the alias, ignoring any further decorative fat arrows.Alias Style 1 - routine aliased to alter code model.
routine cos( radians:Real )->Real => sin( radians + (pi/2) )
Alias Style 2 - routine aliased to generate arbitrary output code.
routine sin( radians:Real )->Real => ${"Math.sin($radians)"}
With both of the above in effect and with a few other assumptions in place, the following Bard code:
position = XY( r*cos(angle), r*sin(angle) )
would produce the following output in Java:
position = new XY( r*Math.sin(angle+(Math.PI/2)), r*Math.sin(angle) );
-
Modules
Modules are analogous to Node.js modules and Java packages. An addition to being an organizational "nice to have", the main reason for Bard's module system is to provide a mechanism for associating native function implementations with required includes and library files.
#========================================================================== # Math.bard #========================================================================== module Math # Specify appropriate header and library files for translated source. $target "C" module.header "#include <math.h>" module.cflags "-lm" $target "C++" module.header "#include <cmath>" module.header "using namespace std;" module.cflags "-lm" $target all routine cos( radians:Real )->Real $if ("C" or "C++") => ${"cos($radians)"} $else => ${"Math.cos($radians)"}
Notes:
- Module definitions can be spread across multiple files.
- The directive
module default
can be used to change scope back to the default module (AKA no module) . - When necessary you can use
ModuleName.
to qualify the scope of an access into a different module.default.
works as well. - You can e.g.
$include "Math.bard"
and callMath.cos(x)
. - You can e.g.
$import "Math.bard"
and callcos(x)
without the "Math." scope qualifier. - In the example above, if
Math.cos
is called from anywhere then source code is generated that includes<math.h>
or<cmath>
etc. If no Math module elements are referenced then those includes are never generated.