BTCL is a simple and easy to use text based weakly typed functional language to construct an object.
BTCL is backward compatible to BTML.
I use the term constructive language to specify a language specialized for object construction. It can be placed somewhere between mere markup and general purpose programming. It offers variables, operators and functions but offers limited interactivity. It it intended to represent a state rather than a process.
Specifically:
- Contiguous code represents one expression (built from expressions or literals). There are no statements.
- An expression represents an object.
- Functions are first class objects.
// String
"some text";
// Variable definition and representation of its value '7'.
x = 7;
// Expression (Evaluates to 14).
1 + 2 * 3 + x;
// Function definition.
f = func( a, b ) { a + b };
// Calling above function. (Evaluates to 7)
n = f( 3, 4 );
// bmath object representation via btml
y = <bcore_arr_s3_s> 1 2 3 </>;
// same result as above (using btcl array initialization)
z = <bcore_arr_s3_s></>( [1,2,3] );
// btcl element initialization
<bcore_main_frame_s></>( .create_log_file = TRUE, .log_file_extension = "log" );
// prints object z to stdout (for messaging, inspection, debugging)
? z; // compact formatting where possible
?? z; // always btml format
// condition (else-part is optional)
if( a >= b ) { a } else { b };
// List with elements 1, 2, 3
[1,2,3];
// concatenation of objects or lists to form a list
a : b; // if a or b is a list, the list is extended (not nested)
// Initialized list with 5 constants; result is [0,0,0,0,0]
5 :: 0;
// Initialized list with 5 elements via initializer-function.
// Index i runs from 0 to 4. The result is [0,2,4,6,8].
5 :: func(i){2*i};
// Mapped list through function; the function is applied to list elements
// forming a new list. The result is [2,4,10].
[1,2,5] :: func(a){2*a};
// embed the content from another file
// relative path is relative to current file location
embed ( "../data/file.btcl" );
BTCL uses C/C++ style comments:
-
//
: Comment until the end of line. -
/* ... */
: Comment block.
BTCL has no distinct statements. Any contiguous code represents just one expression. This can be a composite (or tree) of expressions joined via functions, operators or conditional branches. Non-composite expressions are called Literals.
Although the semicolon ';' is traditionally understood as separator for consecutive statements, in BTCL it is a binary operator combining two consecutive expressions. Hence the semicolon behaves as a binary operator. In BTCL this operator is called Continuation.
a ; b
means:
Evaluate first a
then b
and represent the result of b
. Superficially, this seems to render a
meaningless. However a
can define a variable, which is used in b
at multiple places.
Example:
// 7.0/8.0 expressed via a sigmoid function
( x=7.0; x/(ABS(x)+1) )
More generally: The semicolon operator divides consecutive expressions into context creation and context usage.
Multiple consecutive expressions can be chained up much like a list of statements in a procedural language would be chained. However, the last expression representing the sate of the chain must not be terminated by a semicolon. The advantage compared to a procedural language is that such a chain can be part of any sub-expression.
Each operator has a numeric priority. On chained operations, higher priority operators are evaluated before lower priority operators. Equal operators are evaluated in the chained order (e.g. 3 - 1 - 1 == 2
).
Additionally operators are grouped into priority-groups. Each group is associated with a letter A ... E. Operators in group A have highest and also identical prority.
All other operators have each a unique priority. A higher group letter means lower priority. A lower position within the group means lower priority.
Symbol | Description |
---|---|
. |
Stamp member access |
( |
Function call or stamp modifier; closed by ')' |
Symbol | Description |
---|---|
+ |
Identity |
- |
Arithmetic Negation |
! |
Logic Negation |
? |
Identity: Object is printed to stdout. Leaf objects are printed in compact form. |
?? |
Identity: Object is printed in detail to stdout. |
Symbol | Description |
---|---|
^ |
Arithmetic: exponentiation; result type is f3_t |
/ |
Arithmetic: division |
% |
Arithmetic: modulo division |
** |
Function: Chains two functions |
*.: |
Function: Applies function to unfolded list elements |
*. |
Function: Applies function to unfolded list |
*: |
Function: Applies function to list elements |
* |
Arithmetic,List,Function: multiplication; Unary function application |
- |
Arithmetic: subtraction |
+ |
Arithmetic: addition; Concatenation of strings. |
:: |
List: Spawning Operator |
: |
List: Joining objects to form a list; Concatenation of lists |
== |
Logic: equal |
!= |
Logic: unequal |
>= |
Logic: larger or equal |
> |
Logic: larger |
<= |
Logic: smaller or equal |
< |
Logic: smaller |
& |
Logic: AND |
``` | ``` |
= |
Assignment |
Symbol | Description |
---|---|
; |
Continuation |
Numbers are represented as integer s3_t
or floating point f3_t
.
Integer literals can be expressed as decimal or, when using prefix 0x
as hexadecimal.
Float literals are specified via decimal point (even if the value is a whole number), or using exponential notation.
1023578 // int (s3_t) in decimal form
0x378FCD50 // int (s3_t) in hexadecimal form
123.0 // float (f3_t)
123E1 // float (f3_t)
Binary arithmetic operators return f3_t when one of the operands is f3_t, otherwise they return s3_t.
Sting literals are specified using the C-syntax:
"This is a string"
"\tThis is a string with tab and newline\n"
Operator '+' concatenates string with string or string with number by first converting the number to a string
Operation | Result |
---|---|
"ab"+"cd" | "abcd" |
"ab"+12 | "ab12" |
12+"ab" | "12ab" |
""+12 | "12" |
Logic literals are the keywords true
and false
.
A logic value can be drived from a numeric or boolean expression. Numbers are evaluated false
when they are zero and true
otherwise.
Logic binary operators return bl_t
as result.
A frame represents the lexical context of a code segment. Variables defined inside a frame are visible inside (including sub-frames) but not outside the frame. A variable definition inside a frame masks any variable of the same name outside that frame.
A sub frame is created by using a bracket (...)
or a
block {...}
Any btcl code is inside of the global frame.
if (<condition>) { <expression> }
if (<condition>) { <expression1> } else { <expression2> }
Depending on the condition, the associated block is either evaluated or skipped.
The signature represents the interface of a function. A function is created by joining a signature with a body.
func( <args, comma separated> )
A bracket prioritizes an expression or indicates a function call. A bracket defines a dedicated subframe in which it is evaluated.
( <expression> )
A block is enclosed in braces {...}
.
A block reserves the evaluation of an expression. It is used as part of a function or conditional expression. A block defines a dedicated subframe in which it is evaluated.
Note: A block is not suitable for immediate (framed) evaluation,
as might be in other programming languages. Use the simple
bracket ( ... )
for that purpose.
{ <expression> }
A function is defined by joining a signature with a block.
<signature> : <block>
or
<literal-signature> <literal-block>
f = func( a, b ) { a + b }; // function definition; ':' can be omitted
f( 1, 2 ) // function usage; result is 3
s = func( a, b ); // signature definition
b = { a + b }; // block definition
f = s : b; // function definition
f( 1, 2 ) // function usage; result is 3
The lexical frame is the frame in which a function is used and not where it is defined.
The lexical frame in which the function is defined might not exist at the point of usage. This happens, for example, when the function is defined and returned from another function.
Some languages allow the lexical frame to be the frame of definition, which is deemed more intuitive. Others (e.g. C,C++) circumvent the problem by disallowing function definitions in nested frames.
Best: Avoid making assumptions about frame properties outside the function. Prefer using the function's argument list for passing any and all external data.
Second best: At most assume a global context defined in the root frame.
The keyword self
represents the function in which it is used.
// factorial function using recursion
factorial = func( a )
{
if( a > 1 ) { a * self( a - 1 ) } else { a }
};
factorial( 3 ) // result is 6 (=1*2*3)
Prefer using self
for recursions.
A partial function call is a call that define only a subset of arguments. This creates a new function behaving like the old but with the first arguments replaced by constants and the remaining arguments as the new argument set.
// f has arity 3
f = func( a, b, c )
{
a + b + c;
};
// We define g as f with first two arguments fixed (a=1, b=2).
// g has arity 1
g = f( 1, 2 );
g( 3 ) // result is 6 ( = 1+2+3 )
Binary operators where the left operand is a function.
Operator | Description |
---|---|
* |
Applying a function: f*x == f(x) |
** |
Chaining two functions: (f1**f2)(x) == f2(f1(x)) |
*: |
Transforming a list (f applied to list elements) |
*. |
Applying a function using list elements as arguments: f*.l == f(l.[0], l.[1], ...) |
*.: |
Transforming a list of lists (Applying f *. l.[i] on list elements) |
// List literal
mylist = [1,2,3];
// Element access
mylist.[ 2 ] // is 3 here
// Creating a list by concatenating elements or lists
mylist = a : b : c;
// if any operand is already a list, it will be unfolded and extended
[1,2]:3 == 1:2:3; // this is TRUE
// to explicitly insert a list as element to another list, fold it twice:
a = [1,2];
b = a:[[4,5]];
b == [1,2,[4,5]]; // this is TRUE
Stamps, which are arrays can be initialized with a list by using a modifier:
list = [1,2,3];
arr = <bcore_arr_s3_s></>( list );
The SIZE operator can retrieve the number of elements of a list or array:
list = [1,2,3];
SIZE(list); // this is 3
list.SIZE(); // this is 3
SIZE(<bcore_arr_s3_s>1 2 3</>); // this is 3
Operation a * b
where both operands are lists generate a list product defined as follows
a*b == [ a.[0]:b.[0], a.[0]:b.[1], ..., a.[1]:b.[0], ... a.[n-1]:b.[m-1] ]
Example
[1,2] * [1,2,3] == [ [1,1], [1,2], [1,3], [2,1], [2,2], [2,3] ]
Binary Operator ::
is a multi-tool for constructing or modifying lists or run a recursion. It is used for list based operations using simple rules based on the argument's type.
a-type | b-type | Result |
---|---|---|
number | unary function | List of a elements, each set to b(index) |
number | any other | List of a elements, each set to value b |
list | binary function | Spawned recursion (s. below for details) |
The operation O(Ln,F) with ...
- Ln being a list with n > 0 elements: L[0], ..., L[n-1]
- Lk (k<=n) being the leftbound sublist of Ln with k elements.
- F being a binary function
... is defined as ...
- O(L1,F) = L[0]
- O(Lk,F) = F(O(Lk-1,L[k-1]))
Example
4 :: b == [b,b,b,b];
4 :: func(x){x} == [0,1,2,3];
f = func(a,b){...};
[1,2,3,4] :: f == f(f(f(1,2),3),4);
A general stamp can be instantiated via btml:
anystamp = <bcore_arr_st_s> /* any btml code here */ </>
anystamp = <bcore_arr_st_s/> // brief instantiation
Stamp members can be accessed via '.' operator.
A stamp modifier changes a stamp. The result of the modifier expressioon is a copy of the original stamp with the specified parts modified.
Modifying the entire stamp can be done using a function style syntax:
<bcore_arr_st_s/>( ["a","b","c"] );
The stamp is modified via generic conversion .
Stamp elements can be changed after instantiation via stamp modifiers. Syntactically is has the style of a function call, with elements to be modified identified in the form
.<element_name> = <source>
as assignment expression.
<bcore_main_frame_s/>( .create_log_file = TRUE, .log_file_extension = "log" );
Stamp elements are modified using generic conversion
Generic conversion is a btcl-specific type conversion when general stamps are involved in an assignement/modification operation using following criteria in given order until one succeeds
-
If source and destination have the same type, as simple copy is instigated.
-
If feature 'copy_from' is overloaded for given type it is used.
-
A generic btcl-conversion is used:
- If both types are arrays, the destination array is filled with elements of source.
-
If none of the above succeeds, an error message is generated.
The following operators are hardwired in form of unary functions.
- EXP
- LOG
- LOG2
- LOG10
- SIN
- COS
- TAN
- TANH
- SIGN
- SQRT
- ABS
The following operators are hardwired constants.
- TRUE
- FALSE
- true
- false
- PI
The keyword embed evaluates code from another file.
embed ( "path_to_another_file.txt" )
If the file path is relative it is taken relative to the folder in which the current source is located.
The file is embedded in the current frame (no dedicated frame). This allows defining variables (functions) in the embedded file to be used outside the embedding.
BTCL can access stamp member functions by implementing the features
feature sz_t btcl_function_arity( @* o, tp_t name ) = -1; // return -1 when function 'name' is not defined
feature er_t btcl_function( @* o, tp_t name, bcore_arr_sr_s* args, m sr_s* result ); // must handle all names as indicated by btcl_function_arity
These define a set of functions. Each can be used in btcl like a member function of the stamp that implements the features.
name add_a;
stamp my_stamp
{
f3_t additive = 0;
/* Must return the arity of the function specified by name.
* Must return -1 for any invalid name.
*/
func x_btcl.btcl_function_arity
{
switch( name )
{
case add_a~: = 1; // add_a accepts one argument
default: = -1;
}
= -1;
}
/// Implements all functions which names are listed in the arity function above.
func x_btcl.btcl_function
{
switch( name )
{
case add_a~: sr.from_f3( args.[0].to_f3() + o.addditive ); break;
default: = break; // never reached
}
= 0;
}
}
obj = <my_stamp/>( .additive = 10 );
? obj.add_a( 1 ) // outputs '11'
© Johannes B. Steffens