Sage-Code Laboratory
index<--

Bee Code Structure

Bee has a modular architecture. A source file represents a module. A project can have many modules and can contain other files like configuration files, images and data files. Let's focus first on Bee modules. Each module has extension *.bee and has a distinct role depending on its declaration and location.

Bookmarks:

Next you can learn general concepts about Bee applications:

Projects

A Bee project is a folder with a specific structure. A project contains one or more applications that can run independent of each other on same computer or a group of computers. For example applications can be designed to collaborate with each other into n-tire architecture or can be a group of OS commands for immage manipulation.

project tree

Next project tree contains two applications: client/server and show folders where you should put your code (src+lib) and documentation (doc). This is a recommandation but not a hard rule. You can organize your project better if you are experienced developer.

$pro_home
  |-- bin
  |   |-- client.exe
  |   |-- server.exe
  |
  |-- src
  |   |-- aspect1.bee
  |   |-- aspect2.bee
  |
  |-- lib
  |   |-- library1.bee
  |   |-- library2.bee
  |
  |-- doc
  |   |-- readme.md
  |   |-- index.html
  |
  |-- client.bee
  |-- server.bee

Global Constants

Global constants are using "$" prefix. There are several predefined system constants available in Bee. These constants can be used to locate project files or connect to databases. You can define new global constants at the beginning of driver module or in configuration files.

Constant Environment Description
$bee_home BEE_HOME Bee home folder
$bee_lib BEE_LIB Bee library home
$bee_path BEE_PATH Bee library path
$pro_home N/A Program home folder
$pro_lib N/A Program library home
$pro_mod N/A Program modules home
$pro_log N/A Reporting folder

Compiler directives

Compiler directives are system constants that control the compilation process. You can setup these options in compiler configuration file or in source file. You can not change these options after compilation. They are available for normal control flow statements.

System environment variables are using the same prefix "$". So is easy to remember but it can overvrite our notation. So there is a aconflict. To avoid a conflict, the developers must set-up these variables explicit in configuration file.

Constant Default Description
$precision 0.00001 Control numeric precision
$recursion 10000 Control how deep a recursion before give up
$iteration 0 Control how many iterations before give up
$timeout 10 Control time in seconds before a loop give up
$debug "Off" Control if debug information is included
$echo "Off" Control if statement is printed to console in case of error
$trace "Off" Control if @trace variable is getting populated with information
$dformat "DMY" / "MDY" Control date format: DD/MM/YYYY or MM/DD/YYYY
$tformat "T24" / "T12" Control time format: HH:MM:SS,MS am/pm or HH:MM:SS,MS
$platform "Windows" Alternative: "Linux", "Mac" is the target platform

Notes:

Global Variables

Global variables are defined usually at the beginning of a module outside of any rule.

introspection:

Following system variables are available for debugging:

@timer duration information about last statement
@stack debug information about current call stack
@trace reporting information about statements
@level how deep is the current call stack
@count query count: updated/inserted/deleted records
@query last query statement
@error last error
@threads number of active threads

Notes:

Modules

As we mentioned already Bee is modular. It means one large project can be split into parts. Each part can resolve an aspect of a problem. Therefore is called "aspect". An aspect can use several library modules. An application need an entry point that orchestrate execution of each aspect. This is called the "driver" module. Below we explain each kind of module:

Library Modules

A library module is a file located in "lib" folder having extension *.bee. It is called simply: module. A module can load other modules and can execute its rules multiple times.

Notes:

We use keyword "module" to define a library module name. This declaration is first and is required. We think Explicit is better than implicit, so we name a module. The module name must match the file name. If not, the compiler will raise an error.

Aspect Modules

A good designer will split a large problem into smaller "aspects". An aspect module is similar to a driver. Aspect files are located in "src" folder. Aspects are "resolved" by the driver. Once an aspect is resolved it can be removed from memory.

Notes:

Aspect modules use keyword "aspect" to define its name. File name and aspect name is one and the same. If this is not true, the compiler will give you an error.

Driver Module

One special module that represent the application main module is known in Bee as the driver. This file contains declarations for global constantsglobal variables and the main() rule. Usually a driver receive parameters but have no result.

Notes:

Driver module use keyword "driver" to define its name. File name and driver name are one and the same. Also the driver name becomes the executable name after compilation.

Optional: For small projects that have no aspects you do not have to declare the driver. That is it: compiler should be smart, it will consider a module = driver if thre is no "module" not "aspect" declaration in top of the file. Therefore most of our examples do not use "driver" declaration.

Declarations

Bee is using 6 kind of declarations:

load import a module in global scope
alias declare alternative name for module
type declare data types
make declare variable
stow declare a constant
rule declare named code block

Statements

Each statement start with one imperative keyword:

Examples:

alter modify value or assign new value to variable
read accept input from console into a variable
write register in console cash a string
print output to console with end of new line
apply execute one rule in synchronous mode
begin start execution of a rule in asynchronous mode

Notes:

Code blocks

Statements can be contained in blocks of code.

do start a block statement
when single or multi-path decision
cycle repetitive unconditional block
while repetitive conditional block
given repetitive block with local scope
check multi-path value selector
trial exception handler block

Notes:

Main rule

A module can define "rules". These are sub-programs that can be executed on demand. One special rule is the main() rule that can be defined in the main module. This rule can receive multiple parameters and is automatically executed when program starts.

Example:

# main rule
driver test_main_rule:

rule main(*params ∈ S):
   ** read the number of parameters
   make c := params.count;
   abort if (c = 0);
   
   ** print comma separated parameters
   make i:= 0 ∈ Z;
   while (i < c) do
     write params[i];
     alter i += 1;
     write "," if (i < c);
   cycle;
   ** print the buffer to console
   print;  
return;

Do not try to understand this example. It is just a worm-up code!

Notes:

External code

Modules can be imported from a library folder like this:

Imports:

# loading modules
load $bee_lib.folder_name:(*);     //load all modules from folder
load $bee_lib.folder_name:(x,y,z); //load modules x.bee, y.bee and z.bee

Qualifier Bee use "dot notation" to locate external members. After load the file name becomes the scope qualifier for this notation. It is possible to change the qualifier name using := like in example below:

load  qualifier := $bee_lib.folder_name.module_name; // load a single module
apply qualifier.member_name; // using a fake qualifier

Note:

A module is loaded with a fake qualifier only once. If you do it several times, the last qualifier is used. So it is legal to load all modules from one folder, then for a particular module you overwrite the qualifier name.

All public members must use the specified qualifier or you can use "with" block to suppress the qualifier for a region of code. Using "with" is useful but sometimes not good enough so we have invented the "alias".

Examples:

#load examples
load $runtime.cpp_lib:(*); // load cpp library
load $runtime.asm_lib:(*); // load asm library
load $runtime.bee_lib:(*); // load bee core library
load $program.pro_lib:(*); // load project library

Global scope

One application has a global scope where variables and constants are allocated. Each application file can contribute with global elements that can be merged in this single scope. Global scope can be also called application scope;

Name space

A module has its own scope, that is called name-space where you can define members and statements. Module scope can contain public or private members. Public members start with "." while private members do not have any prefix or suffix.

#define a module
module demo_module:

stow .pi := 3.14;   // public constant
make .v   ∈ N; // public variable
make str := "test"; // private variable

** expression rule foo is private
rule foo(x ∈ N) ∈ N => (x + 1);

** block rule bar is public
rule .bar(x, y ∈ N) => (r ∈ N):
  alter r := x + y;
return;

Loading:

The driver can load numerous libraries. After loading, all public elements of a library can be accessed on demand using dot notation. You can have colisions of names therefore you can use "alias" to rename a rule from one module.

Aliases:

You can create an alias for a specific member to eliminate the qualifier. This rule can be used to "merge" public members into current scope. A member can have one single alias in a module. If you do it multiple times, only the last alias is used. It is a bad practice to change the alias of a member.

Pattern:

alias new_name := qualifier.member_name;

Example:

This example demonstrate how to use a rule from a module named "module_name"

#define program name
driver alias_demo:

** loading a module
load $pro.src.demo_module;

** give alias to module rule
alias sum: demo_module.bar;

** define main rule
rule main():
   ** call rule using dot notation
   make test : demo_module.bar(1,1); // 2

   ** call rule using alias
   make result := sum(1,1); // 2
return;   

Execution

You can execute one aspect in two modes: synchronous mode and asynchronous mode. This is how you can split a large applications into smaller, more manageable parts that can be executed in parallel.

Example:

This is a simple aspect that delay execution for several seconds

# declare module name
aspect test_aspect:

# define a public rule
rule .main(t ∈ Z) => (r ∈ N):
  alter r := t; //prepare the result
  wait t;       //wait for t seconds
return;

Synchron: solve

Let's use the aspect previously defined in synchron mode.

# Solve Aspect
driver solve_aspect:

** execute one module "test" and collect the result
alias test: $pro_src.test_aspect;

/* execute test() and append result 
   at the end of "collect" list */
rule main():
   ** define a collector (list)
   make collect ∈ (N);
   
   solve collect <+ test(30);  
   solve collect <+ test(40); 
   solve collect <+ test(10); 
   solve collect <+ test(20);  
   
   ** the collector is unordered
   print collect; // (30,40,10,20)
return;

Asynchron: defer

Let's use the aspect previously defined in asynchron mode.

# Begin Aspect
driver defer_aspect:

** execute one module "test" and collect the result
alias test: $pro_src.test_aspect;

/* execute test() and append result 
   at the end of "collect" list */
rule main():
   ** define a collector (list)
   make collect ∈ (N);

   defer collect <+ test(30);  
   defer collect <+ test(40); 
   defer collect <+ test(10); 
   defer collect <+ test(20);    
   
   resolve;
   
   ** the collector is ordered 
   print collect; // (10,20,30,40)
return;

Note: By using defer and resolve you can create multi-session applications. Each aspect is executed on a different core, and the application runs them in parallel. The main thread is waithing using resolve keyword for the threads to finish.


Read next: Data Types