B++ Go Library

June 24, 2021 ยท View on GitHub

B++ has 3 libraries: parser, membuild, and compiler. Here is what they do:

Library NameWhat it does
parserParses B++ source code and generates an Abstract Syntax Tree (AST), checks types
membuildCompiles the AST to an array of functions along with some pre-processing, which you can now run
compilerConverts B++ AST into C++ source code

Parser

The parser returns an AST tree when provided with B++ source code. To get the ast tree, you do:

prog, err := parser.Parse(src)
if err != nil {
  panic(err)
}

And thats it! Lets look at the AST tree now.

Everything is a statement, even the function calls. Let's look at the interface for a statement:

type Statement interface {
	Line() int
	Type() DataType
}

You can also find the types in parser/types.go.

Now, lets look at some samples.

All the statements are stored in a Program object. Within the Program, there is an array of statements. Each statement corresponds to a line.

Functions are added by adding to the parsers map. For example, here is the code for the DEFINE statement:

parsers["DEFINE"] = StatementParser{
  Parse: func(args []Statement, pos *Pos) (Statement, error) {
    return &DefineStmt{
      Label:          args[0],
      Value:          args[1],
      BasicStatement: &BasicStatement{pos: pos},
    }, nil
  },
  Signature: []DataType{IDENTIFIER, ANY},
}

A StatementParser has 2 things: it's Parse function, which converts the arguments into a statement, and it's signature, which says what types it accepts.

Data is stored in the Data struct. This is what the Data struct looks like:

type Data struct {
	*BasicStatement
	kind DataType
	Data interface{}
}

The Data value can be type-casted based on the type. To get the type, use <val>.Type().

Types are stored as bitmasks. This allows functions to accept multiple types. For example, a function can add STRING | ARRAY to its signature to accept a string or an array. To check if a type is equal to something, don't use ==. This will most likely not work. Instead use a.IsEqual(b).

Lets look at the types.

Type NameDescription
STRINGStores a string
INTStores an integer
FLOATStores a float64
ARRAYStores an integer
IDENTIFIERStores an identifier, like in a SECTION, DEFINE, or VAR statement
NULLIt's blank, Data is probably nil
VARIADICUsed for variadic arguments, explained below
NUMBERInteger or a float
ANYA collection of all types with values

Now, let's look at the VARIADIC type. This allows accepting any number of arguments to a function. This is used in the ARRAY function. Let's take a look at that:

parsers["ARRAY"] = StatementParser{
  Parse: func(args []Statement, pos *Pos) (Statement, error) {
    return &ArrayStmt{
      Values:         args,
      BasicStatement: &BasicStatement{pos: pos},
    }, nil
  },
  Signature: []DataType{ANY, VARIADIC},
}

Note that the signature for ARRAY has VARIADIC. When a signature is 2 values long, and has VARIADIC as the second value, a function will accept any number of values with the type of the first value. ARRAYs can store any type, so the first value in this case is ANY.

You can check out the Go Reference for more information on each type of statement.

Membuild

Info on how to use membuild coming soon!

Compiler

Info on how to use compiler coming soon!