Bard Syntax: Preprocessor, JS, Aliasing, Modules

Additional syntax changes for new Bard.

  1. Type 'Variant' Reintroduced

    Variant is a type that can store data of any other type. Its implementation will vary
    per target language.

  2. 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).

  3. 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 (or sometimes[EOL]statements[EOL]endSometimes) causes the given statements to (randomly) execute about half the time. This implementation models sometimes as an if 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;
          }
      );
    }
    
  4. 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) );
    
  5. 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:

    1. Module definitions can be spread across multiple files.
    2. The directive module default can be used to change scope back to the default module (AKA no module) .
    3. When necessary you can use ModuleName. to qualify the scope of an access into a different module. default. works as well.
    4. You can e.g. $include "Math.bard" and call Math.cos(x).
    5. You can e.g. $import "Math.bard" and call cos(x) without the "Math." scope qualifier.
    6. 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.