Planck is an esoteric, minimal language created as a hobby project as part of my Compiler Design course during my undergrad. Recently branded and open-sourced just for fun!
<program> ::= <statement_list>
<statement_list> ::= <statement> { <statement> }
<statement> ::= <output>
| <loop>
| <conditional>
| <method_call>
| <assignment>
<output> ::= "%" <identifier>
<loop> ::= "**" <expression> "->" <statement> "$"
<conditional> ::= <expression> "?" <statement>
<method_call> ::= "get" <identifier> "(" <identifier> ")"
<assignment> ::= <identifier> "=" <expression>
<expression> ::= <additive_expression> { <relop> <additive_expression> }
<additive_expression> ::= <term> { <addop> <term> }
<term> ::= <factor> { <mulop> <factor> }
<factor> ::= "(" <expression> ")"
| <identifier>
| <number>
<relop> ::= "<=" | "<" | ">" | ">=" | "==" | "!="
<addop> ::= "+" | "-"
<mulop> ::= "*" | "/"
<identifier> ::= [a-zA-Z_][a-zA-Z0-9_]*
<number> ::= [0-9]+ ("." [0-9]+)?The tokens recognized by the Planck language are:
- Identifiers: Any variable name starting with a letter or underscore, followed by alphanumeric characters, underscores, periods, or special characters like
\#,$, and_. - Numbers: Numeric literals, including integers and floating-point numbers, written as
123or12.34. - Operators: Arithmetic and relational operators like
+,-,*,/,<,<=,>,>=,==, and!=. - Assignment: The assignment operator
=. - Method Call: The keyword
get, used to call a method with syntax likeget x(y). - Output: The
%symbol, used to output the value of a variable. - Loops: Denoted with the
**symbol followed by an expression, then->, a statement, and ending with$. - Conditionals: Denoted with a
?after an expression and followed by a statement. - Delimiters: Characters like parentheses
(), braces{}, and brackets[].
- Assignment and Output:
x = 10
% x
Output:
10
- Loop:
** 5 -> % x $
Explanation:
This defines a loop that runs 5 times and outputs the value of x.
- Conditional:
x = 5 ? % x
Output:
5
- Method Call:
get x(y)
Explanation:
Calls the method x with the argument y.
The Planck interpreter is built purely using C++, and is split into 3 fundamental components: a custom lexer (Flex configuration is also implemented), a parser to evaluate expressions, and a symbol table to manage and track identifiers.
We use Flex to tokenize the input into various tokens like identifiers, numbers, operators, etc. The lexer handles both numeric and identifier-based inputs, as well as arithmetic and comparison operators.
The parser uses recursive descent techniques to break down the tokenized input according to Planck's EBNF grammar. It evaluates statements like assignments, loops, conditionals, and expressions, constructing an abstract syntax tree (AST) and managing control flow. It checks for syntax and semantic errors, ensuring valid execution.
The symbol table in Planck is implemented using a hash table. After implementing both the symbol and symbolTable classes, it was important to integrate the new data structure with the parser.
The symbol table insertion starts in parser::expression_stmt, where assignments are matched. When a variable is assigned, its name, line number, and value are stored as a symbol object and inserted into the table.
To check for variable declarations before usage, a simple lookup() call in the expression production rule ensures that variables exist before they are used. If not found, an error is raised.
