Skip to content
Noel Quintos edited this page Jan 27, 2017 · 32 revisions

Welcome to the Bliss wiki!

The Idea

Bliss is a new language that is as easy to use as python but the speed of C. The syntax is a mix between C (in terms of variable declaration) and python (everything else). A translator to C, called bliss2c, would translate the bliss code to C and then, compiled using C compiler. In other words, bliss provides the abstraction where you could code rapidly and have less mistakes, which then gets translated to C syntax using bliss2c. Then you compile the resulting C-code.

Functionality and features are borrowed from various programming languages (Nim, Julia, Python, C) to allow better abstraction and ease of use.

Why Another Language?

Despite numerous languages out there, I still find much to be desired. Python is the nearest to my ideal: easy to learn, can be productive despite knowing only 5% of it, intuitive syntax that you could easily remember, and lots of ready-made module for almost anything. The only downside is that it is not compiled and I find myself needing that extra speed. The next best candidate is Nim but it is too complicated to my liking. I believe in very simple syntax, with the compiler doing the hard work of figuring out how to deal with it. You will understand what I meant as you read along.

Design objectives are:

  • Simple language syntax that is easy to remember and flows naturally
  • Commonly used patterns are made as simple as possible
  • Commonly used symbols should be easy to type

At this point, no code has been written yet. I am still cataloging the desired features and how they will be translated to the equivalent C-code. I am hoping that somebody with more C experience would join in and propose a better approach, or even propose better usage pattern.

Delimiters

Blocks are grouped by indentation, python-style. The following bliss statement

if 2 < x < 111 < x*x
     z = 5
     y = 0
else
     z = -22
     y = 22

will be translated to this equivalent C statement

if (2 < x && x < 111 && 111 < x*x) {
    z = 5;
    y = 0;
}
else {
    z = -22;
    y = 22;
}  

Notice that unlike python, no need for ':' at the 'if' and 'else' line. Notice also that comparison is more straightforward.

Comments

Use '//' to designate comments as follows:

// this is a comment  
x = 4 // another comment  

Use ''' for longer comments as follows:

'''  
   This is a longer comment  
   spanning several lines  
'''  

bliss2c will simply ignore these comments. None of these comments will appear in the translated C code.

Variable Assignment

The following Bliss code

x = y = z = 5

u = v = f(10)

Would be translated by bliss2c into

z = 5;
y = z;
x = y;

v = f(10);
u = v;

Variable and Function Declaration

Functions are always declared in C-syle and will not be modified by bliss2c

int f(int x, float y)  

Variables need not be declared if it could be derived somewhere else.

x = f(5, 3.4) // no need to declare x

Since we know that f() returns an int, the bliss2c would automatically create the declaration for you as follows:

int x; // <- automatically created by the translator  
x = f(5, 3.4);  

The first assignment will be used as the basis for making declaration

Functions pointers are also automatically declared for each defined functions when function names appear on the right hand side of the assignment operator (=). This is needed so that functions may be passed as parameter. The following bliss code:

int f(int x, float y)
    return x/y

g = f
print(:g(10, 5.0)) // will print 2.0

will be translated into:

int f(int x, float y) {
    return x/y;
}

typedef int (*_bliss_f_i_f)(int, float); // function pointer type definition added
_bliss_f_i_f g = &f;                     // function pointer declaration and address of operator added
printf("%f\n", g(10, 5.0));

bliss2c keeps a dictionary of typedef signatures for all defined function and make appropriate declarations. The format is that it starts with '_bliss_f' followed by the argument data type - 'f' for float, 'i' for int, etc. This would allow it to easily find a match given function signature.

The following function declaration is also allowed:

int g(int x, y, float z)

This is equivalent to:

int g(int x, int y, float z)

Print function

A call like

print(Text to be printed. Age is :age, name is :name, age is :age:5.2)

will be translated to:

printf("Text to be printed. Age is %d, name is %s, age is %5.2f\n", (age, name));

Notice that:

  1. When you print, it will most likely be a text. Hence, by default, you don't even have to quote the text part. It knows that it is text.
  2. Instead of using a place holder for the value and enumerating the values at the end of the print statement, the variable name is simply placed there. The colon (:) is an identifier saying that the next token is to be replaced with that variable's value. With this approach, you will not commit that mistake where the format does not match the number of variables to be printed. I am tempted to use '$' (nim's style) but colon is easier to type than $.
  3. Formatting follows the variable name. Syntax follows that of C.
  4. Formatting string is automatically figured out because data type is known. For example, 'age' is known to be an integer. Hence the formatting string would be %d.

Function Call - named arguments and default values

Bliss code:

int f(int m, int x=1, int y=2)
  return x+y+m

z = f(11)
print(:z) // prints 14

z = f(4, 5)
print(:z) // prints 11

z = f(10, y=5, x=7)
print(:z) // prints 22

v = 10
z = f(40, y=v)
print(:z) // prints 51

This is translated to:

#include <stdio.h>

typedef struct f_struct {
    int m;
    int x;
    int y;
} f_struct;

#define f(...) f((f_struct){.m=0,.x=1,.y=2, __VA_ARGS__})

int (f)(f_struct r) {
    return r.x+r.y+r.m;
}

int main() {                                                                     
    int z;

    z = f(.m=11);
    printf("%d\n", z);

    z = f(.m=4, .x=5);
    printf("%d\n", z);

    z = f(.m=10,.y=5,.x=7);
    printf("%d\n", z);

    int v = 10;
    z = f(.m=40,.y=v);
    printf("%d\n", z);
}

Note that:

  • passed argument is wrapped in a struct which is automatically defined
  • a function wrapper is created that will intercept the call and re-invoke the original function.
  • x and y are replaced with '.x' and '.y', respectively in the function call
  • x and y are replaced with r.x and r.y in the function itself
  • This will allow a call like z = f() but this error will be detected when running bliss2c.

Function Definition in Different Forms

Style 1. As a Mathematical Expression

int f(float x, float y, float z) = x*x - 4*y + z

or more simply,

int f(float x, y, z) = x*x - 4*y + z

Style 2. Typical function definition

int f(float x, y, z)
    return x*x - 4*y + z

Style 3. Last calculation is returned by default if the function returns a value

int f(float x, y, z)
    x*x - 4*y + z

Style 4. Result variable exists by default having the same data type as the returned value and is automatically returned. Doing an explicit return is still allowed but not necessary.

int f(float x, y, z)
    result = x*x - 4*y + z

Once the 'result' variable is used in a function, its value would be returned instead of the last calculation as in Style 3.

NEVER use 'result' variable in your function other than to store the value that you intend to return. You cannot override this variable for anything other than that.

Styles 1, 2 and 3 will be translated as:

int f(float x, float y, float z) {
    return x*x - 4*y + z;
}

Style 4 will be translated as:

int f(float x, float y, float z) {
    int result;
    result = x*x - 4*y + z;
    return result;

Function Calls

Two styles are provided:

The usual one:

value = f(arg1, arg2, arg3)

The alternate:

value = arg1.f(arg2, arg3)

The alternate is useful for classes like if you have an array 'a' and you want to get the length, you could do

a.len() 

instead of

len(a)

bliss2c would translate

value = arg1.f(arg2, arg3)

into

value = f(arg1, arg2, arg3);

Note that if arg1 is an object, 'self' (in python parlance), is actually sent to the function itself.

In general, if you have something like

a.f1(b, c).f2(d, e).f3(g).f4()

gets translated to:

f4(f3(f2(f1(a, b, c), d, e), g))

This allows you to pipe output of one function as input to another function, following a more natural flow of thought.

Function Call Pass by Value and Pass by Reference

By default, parameters passed to functions (even lists or arrays) are copies. For example, this Bliss code:

void f(str s)
    s[0] = 'x'

str v = 'abcd'
f(v)
print(:v)  // would print 'abcd'

This is actually translated to C as:

void f(char *s) {
    s[0] = 'x';
}

int main(void) 
{
  char *v = "abcd";
  f(strdup(v));
  printf("%s\n", v);
}

If you have a variable 'x' and want its value changed through a function call, place ':x' in the function call instead of 'x', and also in the function definition.

void h(int :x, y)  // you want x in the calling body to update x here, so use :x in the declaration
    x = 3 * y

int main(void)
    x = 0
    h(:x, 5)  // you want x here to be updated by h(), so, use ':x' notation
    print(:x) // will print 15

will be translated to:

void h(int *x, y) {
    *x = 3 * y;
}

int main(void)
{
    int x = 0;
    h(&x, 5);
    printf("%d\n", x);
}

Notice that:

  1. When a call is made to function h, ':x' is used. This indicates that you want 'x' in the calling body to be replaced after the function call.

  2. The ':x' in the h argument is simply a confirmation that indeed, you want 'x' in the calling body to be replaced within this function.

  3. Inside the h body definition, you just use 'x' (no colon before it).

The abstraction that is desired is not to be bothered with the *x or &x notation and keep track of whether you are passing an address or value. You have a harder problem to solve and should not be bothered with these little things. In Bliss, you use :x to indicate that you want 'x' value to be updated by the function that is being called, use :x in the function declaration to confirm that this function will indeed update 'x' in the calling body, and then use 'x' everywhere else in the function itself, not bothering whether it is an address or a value - bliss2c will figure it out.

Classes

Here is an example of Bliss class

class Person
    int total_count = 0 // class variable
    init(int self.age, str self.name, str position)
        if position == "manager"
            float self.salary = 10000
        else
            float self.salary = 8000
        ++total_count  // increment the class variable

    int multiply_age_by(int n) = self.age * n

    float raise_salary(float percent)
        self.salary * (1 + percent/100)

// How to use the class
p = Person(12, 'John', 'manager')
q = Person(31, 'Peter', 'clerk')
print(ten times age = :p.multiply_age_by(10) for :p.name)
print(total_count = :Person.total_count)
print(raised salary by 10% = :p.raise_salary(10) for :p.name)
print(ten times age = :q.multiply_age_by(10) for :q.name)
print(total_count = :q.total_count)
print(raised salary by 10% = :q.raise_salary(10) for :q.name)

Notice that in the class initialization, instance variables are identified with "self", to distinguish them from, say, temporary variables, that are not supposed to be saved with that instance. Values also goes directly to the instance variables by mere inclusion of them in the "init" argument. In other words,

init(int self.age, char *self.name)

is equivalent to:

init(int age, char *name)
    self.age = age
    self.name = name

This is translated to C as:

#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>

int Person_total_count = 0;

typedef struct Person Person;
typedef int (*_bliss_f_Person_i)(Person, int);
typedef float (*_bliss_f_Person_f)(Person, float);

struct Person {
    int age;
    char *name;
    float salary;
    _bliss_f_Person_i _bliss_Person_multiply_age_by;
    _bliss_f_Person_f Person_raise_salary;
};

int _bliss_Person_multiply_age_by(Person p, int n) {
    return p.age * n;
}

float _bliss_Person_raise_salary(Person p, float percent) {
        return p.salary * (1 + percent/100);
}


Person Person_init(int age, char *name, char *position) {
    Person p;
    p.name = malloc((1+strlen(name))*sizeof(char));
    assert(p.name != NULL);
    strcpy(p.name, name);
    p.age = age;
    if (!strcmp(position, "manager")
        p.salary = 10000;
    else
        p.salary = 8000;
    p.multiply_age_by = &Person_multiply_age_by;
    p.raise_salary = &Person_raise_salary;
    ++Person_total_count;
    return p;
}

int main(void)
{
    Person p;
    Person q;
    p = Person_init(12, "John", 10000.0);
    q = Person_init(31, "Peter", 40000.0);
    printf("ten time age = %d for %s\n", p.multiply_age_by(p, 10), p.name);
    printf("total count = %d\n", Person_total_count);
    printf("raised salary by 10percent = %f for %s\n", p.raise_salary(p, 10.0), p.name);
    printf("ten time age = %d for %s\n", q.multiply_age_by(q, 10), q.name);
    printf("raised salary by 10percent = %f for %s\n", q.raise_salary(q, 10.0), q.name);
}

List or Arrays

In general, declaration below

list xyz v

Is translated in C as

 typedef struct {
   _bliss_class_xyz **value;
   long int len;
} _bliss_list_xyz;

_bliss_list_xyz v;

Then, access such as

m = v[i]

is translated to C as:

m = v->value[i];

assignment such as

v[i] = n

is translated to C as:

memcpy(&(v->value[i]), &n, sizeof(_bliss_class_xyz));

Iteration such as

for u in v
    sum += u

is translated to C as

for(int _i=0, _i<v->len, _i++) {
    sum += v->value[i];
}