-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
42 changed files
with
991 additions
and
2,301 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
version: 2 | ||
|
||
build: | ||
os: ubuntu-latest | ||
tools: | ||
python: "3.12" | ||
|
||
python: | ||
install: | ||
- requirements: docs/requirements.txt | ||
|
||
mkdocs: | ||
configuration: mkdocs.yaml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
# The preprocessor | ||
|
||
Blombly's preprocessor can understand several instructions that transform the source code before compilation into `.bbvm` files. Three transformations are available: dependencies that make a source code file include another's code, macros that add to the language's grammar with higher-level expressions, and specification property declarations. | ||
|
||
Of those, specifications are already been described as a mechanism that attaches final values to code blocks by evaluating them just after the block's definition. | ||
|
||
## Include and modules | ||
|
||
Dependencies on other `.bb` files are stated with a pattern like so: | ||
|
||
```java | ||
// main.bb | ||
#include "std/oop" | ||
``` | ||
|
||
This statement inlines the contents of the file `std/oop.bb`. Dependencies allow code modularization without loading overheads, as the compiled .bbvm packs all necessary instructions to run directly. Note that dependencies should not declare specifications, as these are the provenance of code blocks meant to run dynamically instead of immediately upon import. When creating reusable libraries, prefer constructing modules and final objects (this is automated with the module keyword of `std/oop`). | ||
|
||
Below is an example of what a module may look like. Instead of specifications, `final` properties are set for documentation and versioning. However, methods (which are essentially code blocks meant to be called) obtain specifications. Notice that those specifications can depend on the library's other properties. To use the module in your code, include the module and call the relevant methods. | ||
|
||
```java | ||
// main.bb | ||
#include "mylib" | ||
|
||
mylib.tests(); | ||
print(mylib.add(1, 2)); | ||
``` | ||
|
||
```java | ||
// mylib.bb | ||
module mylib { | ||
final name = "mylib"; | ||
final author = "Your Name"; | ||
final doc = "This is a simple numeric interface"; | ||
final version = 1.0; | ||
|
||
fn add(x, y) { | ||
#spec doc = "Computes an addition in " + name; | ||
#spec version = version; | ||
return x + y; | ||
} | ||
|
||
fn abs(x) { | ||
#spec doc = "Computes the absolute value in " + name; | ||
#spec version = version; | ||
if (x < 0) return -x; | ||
return x; | ||
} | ||
|
||
final tests = { | ||
if (add(1, 2) != 3) fail("Invalid addition"); | ||
if (abs(-1) != 1) fail("Invalid abs"); | ||
print("Tests successful."); | ||
} | ||
} | ||
``` | ||
|
||
|
||
## Macros | ||
|
||
Macros are transformations for reducing boilerplate code. They are declared with statements of the form `#macro (@expression) = (@transformation);` | ||
Both the expression and transformation parts consist of fixed "keyword" tokens and named wildcard tokens, prepended with att (`@`). | ||
Wildcards represent any sequence of tokens and matched between the expression and the transformation. | ||
To support faster compilation and improve comprehension, the first token of the expression needs to be a keyword (e.g., `fn @name (@args)` is valid). | ||
|
||
Here is an example of how macros simplify code, defining a class and function with macros from the std/oop module. | ||
Using these, one can now define classes, modules, and functions more concisely: | ||
|
||
```java | ||
// oop.bb | ||
#macro (class @name {@code}) = (final @name = {@code}); | ||
#macro (fn @name(@args) {@code}) = (final @name(@args) = {@code}); | ||
#macro (module @name {@code}) = (final @name = new {@code}); | ||
``` | ||
|
||
```java | ||
// main.bb | ||
#include "oop.bb" | ||
|
||
class Finder { | ||
fn contains(number) { | ||
i = 2; | ||
i_max = int(number^0.5); | ||
while (i <= i_max) { | ||
if (number % i == 0) return false; | ||
i = i + 1; | ||
} | ||
return true; | ||
} | ||
|
||
fn next() { | ||
while (true) { | ||
this.number = this.number + 1; | ||
if (this.contains(number)) return this.number; | ||
} | ||
} | ||
} | ||
|
||
finder = new {Finder: number = 10;} | ||
|
||
print(finder.next()); | ||
print(finder.next()); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# Servers | ||
|
||
## Routes | ||
|
||
Blombly offers the ability to set up REST services. | ||
Instantiating a server is as simpleas calling `routes=std::server(port)`, | ||
where the port is provided. | ||
Treat the generated server as a map from resource location strings to code blocsk | ||
to be called when the respective resource is requested. Blocks called this | ||
ways should returned either a string plain text or a request result struct (see below). | ||
Parts of resource names that reside in angular brackets `<...>` indicate that the respective | ||
part of therequest should be treated as a string argument to the callable. | ||
|
||
For example, the following snippet uses the standard library to create the alias `server` | ||
for `std::server` and then redirects `echo/<input>` to echo the provided input; | ||
run code and open the browser and visit `localhost:8000/echo/MyTest` to see this in action. | ||
|
||
```java | ||
// main.bb | ||
#include "libs/std" | ||
routes = server(8000); | ||
routes["/echo/<input>"] = {return input;} | ||
while(true) {} // wait indefinitely | ||
``` | ||
|
||
|
||
## Request data | ||
|
||
In addition to the keyword argument values obtained by parsing the request, calls | ||
to route code blocks may be enriched with several positional arguments, if available. | ||
These are listed below: | ||
|
||
| Argument | Type | Details | | ||
| -------- | ---- | ----------- | | ||
| method | str | "GET" or "POST". | | ||
| content | str | Any received message content. This requires further parsing. | | ||
| ip | str | The user's ip address. | | ||
| http | str | The http protocol's version. | | ||
| query | str | Any query parameters; those following after the questionmark (`?`). | | ||
| ssl | bool | Whether HTTPS or WS is used (SSL/TLS used). | | ||
| uri | str | The request's full uri. | | ||
|
||
|
||
```java | ||
// main.bb | ||
#include "libs/std" | ||
enable std; | ||
|
||
new { | ||
value = 0; | ||
routes = server(8000); | ||
routes["/hi/<number>"] = { | ||
if((number as int(number))==false) | ||
return "The number of hi must be an integer."; | ||
if(number<=0) | ||
return "Need a positive number of hi. Why must you take them away? :-("; | ||
this.value = this.value + 1; | ||
return str(this.value+number)+" hello your "+str(number)+" hi"; | ||
} | ||
} | ||
|
||
print("Give me some greetings at localhost:8000/hi/<number>"); | ||
while(true) {} // wait indefinitely | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# Signals | ||
|
||
Blombly has a success-fail mechanism to capture algorithm outcomes. This consists of two parts. First is a `try` statement, which is applied on a block of code to obtain the outcome of any internal `return` or `fail` signals; these are otherwise ways of escaping calling methods by immediately returning values or creating errors, but `try` ensures that all such signals are intercepted. Its outcome is the intercepted signal and can be assigned to a variable for further handling, like so: | ||
|
||
```java | ||
result = try { | ||
x = x + 1; // invalid code that creates an error (missing variable x) | ||
} | ||
print(result); | ||
print("Successfully intercepted the above error"); | ||
``` | ||
|
||
Similarly to other control flow statements, brackets are optional for a `try` that is applied to only one statement. Omitting brackets can be used to create concise variations of other control flow statements. For example, one can compute exiting values, break loops, or detect if loops were not broken. For example, an one-liner that computes the sign of a variable `x` is `sgn = try if(x >= 0) return 1 else return -1;` | ||
|
||
We typically want to differentiate between those try results that hold errors and those that do not. This is achieved through the catch statement; this is near-identical to a conditional statement with the only difference being that it checks whether the condition is an error (instead of checking a boolean value). An example usage of this statement is demonstrated below. In this, the try statement is responsible for intercepting the returned value of i, which stops the loop immediately. | ||
|
||
If no value was returned, try assigns an error value to the result. | ||
|
||
```java | ||
least = 4; | ||
i = 0; | ||
result = try while (i <= 100) { | ||
i = i + 3; | ||
if (i >= least) return i; | ||
} | ||
print("Finished searching."); | ||
catch (result) fail("Found nothing: " + str(result)); | ||
print("The least multiple of 3 in range [" + str(least) + ", 100] is: " + str(result)); | ||
``` | ||
|
||
If nothing is intercepted by a try statement, then a missing value is yielded. In this case, use the as syntax like normally to identify if there is a return value. For example, the following snippet inlines an algorithm and checks that it returns a value. Remove the return statement from the algorithm code block (leave it empty) and see how the fail statement correctly creates an error. | ||
|
||
```java | ||
algorithm = {return "result";} | ||
|
||
if (x as try algorithm:) | ||
print(x) // ; cannot be the last symbol before else | ||
else | ||
fail("Nothing was returned"); | ||
``` | ||
|
Oops, something went wrong.