Switch to curly braces for scoping instead of indentation

This commit is contained in:
Nikita Lisitsa 2026-07-27 16:12:56 +03:00
parent 9337490f5b
commit 893aa5d979
21 changed files with 692 additions and 728 deletions

View file

@ -187,7 +187,7 @@ int main(int argc, char ** argv)
if (dump_ast)
{
std::cout << "Input file " << filenames.back() << " AST dump:\n\n";
if (auto function_definition = std::get_if<ast::function_definition>(parsed.back().get()))
if (auto function_definition = std::get_if<ast::function_definition>(root.get()))
ast::print(std::cout, *function_definition->statements);
std::cout << "\n" << std::flush;
}

View file

@ -4,81 +4,92 @@ import components
import ecs
const x = 10s // deduced type i16
var y = 14u // deduced type u32
var z: f64 = 3.14l
mut y = 14u // deduced type u32
mut z: f64 = 3.14l
func fma(x: f32, y: f32, z: f32) -> f32:
return x * y + z
func fma(x: f32, y: f32, z: f32) -> f32 {
return x * y + z
}
struct vec2:
x: f32
y: f32
struct vec2 {
x: f32
y: f32
}
// pass by value
func length(v: vec2) -> f32:
return math.sqrt(v.x * v.x + v.y * v.y)
func length(v: vec2) -> f32 {
return math.sqrt(v.x * v.x + v.y * v.y)
}
// return type deduced as u64
func merge(x: u32, y: u32):
return (x as u64) or ((y as u64) << 32)
func merge(x: u32, y: u32) -> u64 {
return (x as u64) or ((y as u64) << 32)
}
var v = vec2(10, 20)
mut v = vec2(10, 20)
length(v)
// can be called using method syntax
v.length()
// function pointers
var my_func = fma // deduced type (f32, f32, f32) -> f32
mut my_func = fma // deduced type (f32, f32, f32) -> f32
// pass by reference/pointer with *
// TODO: const pointer?
func my_system(event: events.update, position: *components.position, velocity: *components.velocity):
position += event.dt * velocity
func my_system(event: events.update, position: components.position mut*, velocity: components.velocity*) {
position += event.dt * velocity
}
func attach(dispatcher: *ecs.dispatcher):
// TODO: how does it work? C++-style variadic templates? Oh no...
dispatcher.system(my_system)
func attach(dispatcher: ecs.dispatcher*) {
// TODO: how does it work? C++-style variadic templates? Oh no...
dispatcher.system(my_system)
}
// objects with methods
struct rectangle:
width: i32
height: i32
struct rectangle {
width: i32
height: i32
}
func extend(r: &rectangle, size: i32):
r.width += size
r.height += size
func extend(r: rectangle mut*, size: i32) {
r.width += size
r.height += size
}
var r = rectangle(10, 12)
mut r = rectangle(10, 12)
r.extend(5)
// named initializers
var r2 = rectangle(width = 20, height = 30)
mut r2 = rectangle(width = 20, height = 30)
// regular pointers
var ptr: *i32 = null
var x = 15
mut ptr: i32* = null
mut x = 15
ptr = &x
// field/method access using pointers is the same as with values
var sptr: *rectangle = &r
mut sptr: rectangle mut* = &r
r.width *= 2
// simple generics
struct array(T):
data: *T
size: u64
struct array(T) {
data: *T
size: u64
}
// TODO: constructors? destructors?
func new(self: array(T), size: u64):
return array(T)(data = mem.alloc(size * sizeof(T)), size = size)
func new(self: array(T), size: u64) {
return array(T)(data = mem.alloc(size * sizeof(T)), size = size)
}
// TODO: static arrays?
// TODO: move-only types? alloc returns smth like unique ptr?
struct kvpair(K, V):
key: K
value: V
struct kvpair(K, V) {
key: K
value: V
}
struct arraymap(K, V):
values: array(kvpair(K, V))
struct arraymap(K, V) {
values: array(kvpair(K, V))
}

View file

@ -1,7 +1,8 @@
foreign func sin(x: f64) -> f64
foreign func cos(x: f64) -> f64
func test(x: f64) -> f64:
func test(x: f64) -> f64 {
let s = sin(x)
let c = cos(x)
return s * s + c * c
}

View file

@ -1,40 +1,48 @@
func print(c: u8):
func print(c: u8) {
foreign func putchar(c: i32) -> i32
putchar(c as i32)
}
func print_i32(x: i32):
if x < 0:
func print_i32(x: i32) {
if x < 0 {
print('-')
print_i32(-x)
return
if x >= 10:
}
if x >= 10 {
print_i32(x / 10)
}
print('0' + (x % 10 as u8))
}
func print_f32(x: f32):
if x < 0.0:
func print_f32(x: f32) {
if x < 0.0 {
print('-')
print_f32(-x)
return
}
foreign func floorf(x: f32) -> f32
let floor = floorf(x) as i32
print_i32(floor)
print('.')
mut y = x - (floor as f32)
mut i = 0
while i < 5:
while i < 5 {
y = y * 10.0
let yfloor = floorf(y) as i32
print('0' + (yfloor as u8))
y = y - (yfloor as f32)
i = i + 1
}
}
struct vec3:
struct vec3 {
x: f32
y: f32
z: f32
}
func print_vec3(v: vec3):
func print_vec3(v: vec3) {
print('(')
print_f32(v.x)
print(',')
@ -42,31 +50,39 @@ func print_vec3(v: vec3):
print(',')
print_f32(v.z)
print(')')
}
func dot(a: vec3, b: vec3) -> f32:
func dot(a: vec3, b: vec3) -> f32 {
return a.x * b.x + a.y * b.y + a.z * b.z
}
func add(a: vec3, b: vec3) -> vec3:
func add(a: vec3, b: vec3) -> vec3 {
return vec3(a.x + b.x, a.y + b.y, a.z + b.z)
}
func mult(a: vec3, b: f32) -> vec3:
func mult(a: vec3, b: f32) -> vec3 {
return vec3(a.x * b, a.y * b, a.z * b)
}
func normalized(v: vec3) -> vec3:
func normalized(v: vec3) -> vec3 {
foreign func sqrtf(x: f32) -> f32
return mult(v, 1.0 / sqrtf(dot(v, v)))
}
struct ray:
struct ray {
origin: vec3
direction: vec3
}
func intersect_plane(ray: ray, normal: vec3, value: f32) -> f32:
func intersect_plane(ray: ray, normal: vec3, value: f32) -> f32 {
return (value - dot(ray.origin, normal)) / dot(ray.direction, normal)
}
func test() -> i32[3]:
func test() -> i32[3] {
return [70, 60, 50]
}
func print_i32_3(a: i32[3]):
func print_i32_3(a: i32[3]) {
print('[')
print_i32(a[0])
print(',')
@ -74,6 +90,7 @@ func print_i32_3(a: i32[3]):
print(',')
print_i32(a[2])
print(']')
}
mut a = [1, 2, 3]
a = test()

View file

@ -1,11 +1,13 @@
func print(c: u8):
func print(c: u8) {
foreign func putchar(c: i32) -> i32
putchar(c as i32)
}
func test() -> i32:
func test() -> i32 {
global mut x = 0
x += 1
return x
}
print('0' + (test() as u8))
print('0' + (test() as u8))

View file

@ -1,32 +1,37 @@
foreign func putchar(c: i32) -> i32
func print(c: u8):
func print(c: u8) {
foreign func putchar(c: i32) -> i32
putchar(c as i32)
}
func mandelbrot():
func mandelbrot() {
let width = 120
let height = 40
mut y = 0
while y < height:
while y < height {
mut x = 0
while x < width:
while x < width {
let cx = (x as f32 + 0.5) / (width as f32) * 2.5 - 2.0
let cy = (y as f32 + 0.5) / (height as f32) * 2.0 - 1.0
mut tx = 0.0
mut ty = 0.0
mut i = 0
while i < 100 && (tx * tx + ty * ty < 4.0):
while i < 100 && (tx * tx + ty * ty < 4.0) {
let newx = tx * tx - ty * ty + cx
ty = 2.0 * tx * ty + cy
tx = newx
i = i + 1
if i == 100:
}
if i == 100 {
print('X')
else:
} else {
print(' ')
}
x = x + 1
}
y = y + 1
print('\n')
}
}
mandelbrot()

File diff suppressed because it is too large Load diff

View file

@ -1,11 +1,15 @@
func g() -> u16:
func g() -> u16 {
return g()
}
func h() -> bool:
func h() -> bool {
return h()
}
func test_and() -> u16:
func test_and() -> u16 {
return 0us && g()
}
func test_or() -> u16:
func test_or() -> u16 {
return 65535us || g()
}

View file

@ -1,10 +1,13 @@
struct vec2f:
struct vec2f {
x: f32
y: f32
}
struct body:
struct body {
position: vec2f
rotation: f32
}
func move_x(b: body mut*, delta: f32):
(*b).position.x = (*b).position.x + delta
func move_x(b: body mut*, delta: f32) {
b.position.x = b.position.x + delta
}

View file

@ -1,11 +1,13 @@
// Vectors
struct vec2:
struct vec2 {
x : f32
y : f32
}
func add(a : vec2, b : vec2) -> vec2:
func add(a : vec2, b : vec2) -> vec2 {
return vec2(a.x + b.x, a.y + b.y)
}
mut v = add(vec2(1.0, 2.0), vec2(3.0, 4.0))
v.x = -v.x
@ -13,34 +15,39 @@ v.y = -v.y
// Factorial
func factorial(n : u32) -> u32:
if n == 0u:
return 1u
func factorial(n : u32) -> u32 {
if n == 0u { return 1u }
return n * factorial(n - 1u)
}
let factorial10 = factorial(10u)
// Fibonacci
func fib(n : u32) -> u32:
func fib(n : u32) -> u32 {
// Slow implementation with
// exponentially-growing recursion tree
if n == 0u | n == 1u:
return n // base case
if n == 0u | n == 1u {
// Base case
return n
}
return fib(n - 1u) + fib(n - 2u)
}
let fib10 = fib(10u)
func h() -> u32:
func h() -> u32 {
return 0u
}
func f() -> u32:
func f() -> u32 {
return h()
}
func g() -> u32:
func h() -> u32:
return 1u
func g() -> u32 {
func h() -> u32 { return 1u }
return f()
}
// Should equal 0u, but equals 1u due to an error in
// how the interpreter resolves functions & variables

View file

@ -20,29 +20,6 @@ namespace pslang::ast
ast::location location;
};
using pre_statement_impl = std::variant<
expression_ptr,
assignment,
variable_declaration,
if_block,
else_block,
else_if_block,
while_block,
break_statement,
continue_statement,
function_definition,
foreign_function_declaration,
return_statement,
field_definition,
struct_definition
>;
struct pre_statement
: pre_statement_impl
{
using pre_statement_impl::pre_statement_impl;
};
using statement_impl = std::variant<
expression_ptr,
assignment,
@ -63,7 +40,6 @@ namespace pslang::ast
using statement_impl::statement_impl;
};
location get_location(pre_statement const & statement);
location get_location(statement const & statement);
}

View file

@ -6,23 +6,15 @@
namespace pslang::ast
{
struct pre_statement;
struct statement;
using pre_statement_ptr = std::shared_ptr<pre_statement>;
using statement_ptr = std::shared_ptr<statement>;
struct pre_statement_list
{
std::vector<pre_statement_ptr> statements;
};
struct statement_list
{
std::vector<statement_ptr> statements;
};
using pre_statement_list_ptr = std::shared_ptr<pre_statement_list>;
using statement_list_ptr = std::shared_ptr<statement_list>;
}

View file

@ -22,11 +22,6 @@ namespace pslang::ast
}
location get_location(pre_statement const & statement)
{
return std::visit(get_location_visitor{}, statement);
}
location get_location(statement const & statement)
{
return std::visit(get_location_visitor{}, statement);

View file

@ -1,6 +1,6 @@
#pragma once
#include <pslang/parser/indented_statement.hpp>
#include <pslang/ast/statement_fwd.hpp>
namespace pslang::ast
{
@ -15,7 +15,7 @@ namespace pslang::parser
struct context
{
ast::location & location;
indented_statement_list & result;
ast::statement_list_ptr & result;
};
}

View file

@ -0,0 +1,10 @@
#pragma once
#include <pslang/ast/statement_fwd.hpp>
namespace pslang::parser
{
ast::statement_list_ptr finalize(ast::statement_list_ptr statements);
}

View file

@ -1,23 +0,0 @@
#pragma once
#include <pslang/ast/statement_fwd.hpp>
#include <vector>
namespace pslang::parser
{
struct indented_statement
{
std::size_t indentation;
ast::pre_statement_ptr statement;
};
struct indented_statement_list
{
std::vector<indented_statement> statements;
};
ast::statement_list_ptr finalize(indented_statement_list statements);
}

View file

@ -19,7 +19,7 @@ using bp = ::pslang::parser::bison::parser;
ctx.location.step();
%}
[ ]+ { ctx.location.step(); }
[ \r\t]+ { ctx.location.step(); }
"//"[^\n]* { return bp::make_comment(ctx.location); }
@ -60,7 +60,6 @@ f64 { return bp::make_f64(ctx.location); }
[a-zA-Z_]+[a-zA-Z0-9_]* { return bp::make_name(yytext, ctx.location); }
"\n" { auto old_location = ctx.location; ctx.location.move_lines(1); return bp::make_newline(old_location); }
"\t" { return bp::make_indent(ctx.location); }
"=" { return bp::make_assignment(ctx.location); }
":" { return bp::make_colon(ctx.location); }
"," { return bp::make_comma(ctx.location); }
@ -69,6 +68,8 @@ f64 { return bp::make_f64(ctx.location); }
")" { return bp::make_rparen(ctx.location); }
"[" { return bp::make_lbracket(ctx.location); }
"]" { return bp::make_rbracket(ctx.location); }
"{" { return bp::make_lbrace(ctx.location); }
"}" { return bp::make_rbrace(ctx.location); }
"+=" { return bp::make_plus_assignment(ctx.location); }
"-=" { return bp::make_minus_assignment(ctx.location); }
"*=" { return bp::make_asterisk_assignment(ctx.location); }

View file

@ -20,7 +20,6 @@
%code requires {
#include <pslang/parser/indented_statement.hpp>
#include <pslang/ast/statement.hpp>
namespace pslang::parser {
@ -75,8 +74,7 @@ template <typename T>
%define api.token.prefix {tok_}
%token newline "newline"
%token indent "indentation"
%token comment
%token comment "comment"
%token assignment "="
%token colon ":"
%token comma ","
@ -85,6 +83,8 @@ template <typename T>
%token rparen ")"
%token lbracket "["
%token rbracket "]"
%token lbrace "{"
%token rbrace "}"
%token plus "+"
%token minus "-"
%token asterisk "*"
@ -184,10 +184,12 @@ template <typename T>
%precedence else
%precedence lbracket
%type <indented_statement_list> indented_statement_list
%type <indented_statement> statement_line
%type <std::size_t> indentation
%type <ast::pre_statement> statement
%type <ast::statement_list> statement_list
%type <ast::statement_list_ptr> statement_block
%type <ast::statement> statement_line
%type <ast::statement> statement
%type <ast::if_chain> if_chain
%type <ast::if_chain::block> single_if
%type <std::vector<ast::function_declaration::argument>> function_declaration_argument_list
%type <std::vector<ast::function_declaration::argument>> nonempty_function_declaration_argument_list
%type <ast::function_declaration::argument> function_declaration_single_argument
@ -199,6 +201,8 @@ template <typename T>
%type <types::primitive_type> primitive_type
%type <std::vector<ast::type_ptr>> function_paren_type_list
%type <std::vector<ast::type_ptr>> two_or_more_type_list
%type <std::vector<ast::field_definition>> field_definition_list
%type <ast::field_definition> field_definition
%type <ast::expression> expression
%type <ast::expression> postfix_expression
%type <ast::expression> base_expression
@ -209,27 +213,26 @@ template <typename T>
%%
module
: indented_statement_list end { ctx.result = $1; }
: statement_list end { ctx.result = std::make_unique<ast::statement_list>($1); }
;
indented_statement_list
: statement_line { indented_statement_list tmp; tmp.statements.push_back(std::move($1)); $$ = std::move(tmp); }
statement_list
: statement_line { ast::statement_list tmp; tmp.statements.push_back(std::make_unique<ast::statement>($1)); $$ = std::move(tmp); }
| empty_line { $$ = {}; }
| indented_statement_list newline statement_line { auto tmp = $1; tmp.statements.push_back(std::move($3)); $$ = std::move(tmp); }
| indented_statement_list newline empty_line { $$ = $1; }
| statement_list newline statement_line { auto tmp = $1; tmp.statements.push_back(std::make_unique<ast::statement>($3)); $$ = std::move(tmp); }
| statement_list newline empty_line { $$ = $1; }
;
statement_block
: lbrace statement_list rbrace { $$ = std::make_unique<ast::statement_list>($2); }
;
statement_line
: indentation statement optional_comment { $$ = indented_statement{$1, std::make_unique<ast::pre_statement>($2)}; }
: statement optional_comment { $$ = $1; }
;
empty_line
: indentation optional_comment
;
indentation
: indent indentation { $$ = $2 + 1ull; }
| %empty { $$ = 0ull; }
: optional_comment
;
optional_comment
@ -251,18 +254,25 @@ statement
| expression left_shift_assignment expression { auto lhs = std::make_shared<ast::expression>($1); $$ = ast::assignment{ lhs, std::make_shared<ast::expression>(ast::binary_operation{ ast::binary_operation_type::left_shift, lhs, std::make_unique<ast::expression>($3), @$ }), @$ }; }
| expression right_shift_assignment expression { auto lhs = std::make_shared<ast::expression>($1); $$ = ast::assignment{ lhs, std::make_shared<ast::expression>(ast::binary_operation{ ast::binary_operation_type::right_shift, lhs, std::make_unique<ast::expression>($3), @$ }), @$ }; }
| variable_declaration { $$ = $1; }
| if expression colon { $$ = ast::if_block{std::make_unique<ast::expression>($2), @$}; }
| else colon { $$ = ast::else_block{@$}; }
| else if expression colon { $$ = ast::else_if_block{std::make_unique<ast::expression>($3), @$}; }
| while expression colon { $$ = ast::while_block{std::make_unique<ast::expression>($2), {}, @$, @$}; }
| if_chain { $$ = $1; }
| while expression statement_block { $$ = ast::while_block{std::make_unique<ast::expression>($2), $3, @$, @$}; }
| break { $$ = ast::break_statement{@$}; }
| continue { $$ = ast::continue_statement{@$}; }
| func name lparen function_declaration_argument_list rparen function_return_type colon { $$ = ast::function_definition{{$2, $4, $6, @$}, {}}; }
| func name lparen function_declaration_argument_list rparen function_return_type statement_block { $$ = ast::function_definition{{$2, $4, $6, @$}, $7}; }
| foreign func name lparen function_declaration_argument_list rparen function_return_type { $$ = ast::foreign_function_declaration{{$3, $5, $7, @$}}; }
| return expression { $$ = ast::return_statement{std::make_unique<ast::expression>($2), @$}; }
| return { $$ = ast::return_statement{nullptr, @$}; }
| struct name colon { $$ = ast::struct_definition{$2, {}, @$, @$}; }
| name colon type_expression { $$ = ast::field_definition{$1, std::make_unique<ast::type>($3), @$}; }
| struct name lbrace field_definition_list rbrace { $$ = ast::struct_definition{$2, $4, merge(@1, @2), @$}; }
;
if_chain
: single_if { $$ = ast::if_chain{{$1}, @$}; }
| if_chain else single_if { auto tmp = $1; tmp.blocks.push_back($3); tmp.location = @$; $$ = std::move(tmp); }
| if_chain else statement_block { auto tmp = $1; tmp.blocks.push_back({nullptr, $3, @2, merge(@2, @3)}); tmp.location = @$; $$ = std::move(tmp); }
;
single_if
: if expression statement_block { $$ = ast::if_chain::block{std::make_unique<ast::expression>($2), $3, merge(@1, @2), @$}; }
;
function_declaration_argument_list
@ -301,6 +311,17 @@ variable_keyword
| mut { $$ = ast::value_category::_mutable; }
;
field_definition_list
: field_definition { std::vector<ast::field_definition> tmp; tmp.push_back(std::move($1)); $$ = std::move(tmp); }
| empty_line { $$ = {}; }
| field_definition_list newline field_definition { auto tmp = $1; tmp.push_back(std::move($3)); $$ = std::move(tmp); }
| field_definition_list newline empty_line { $$ = $1; }
;
field_definition
: name colon type_expression { $$ = ast::field_definition{$1, std::make_unique<ast::type>($3), @$}; }
;
type_expression
: unit { $$ = types::unit_type{}; }
| primitive_type { $$ = ast::type($1); }

View file

@ -1,4 +1,4 @@
#include <pslang/parser/indented_statement.hpp>
#include <pslang/parser/finalize.hpp>
#include <pslang/parser/error.hpp>
#include <pslang/ast/statement.hpp>
#include <pslang/ast/statement_visitor.hpp>
@ -11,265 +11,50 @@ namespace pslang::parser
namespace
{
struct fill_location_visitor
: ast::statement_visitor<fill_location_visitor>
void validate(ast::statement_list_ptr statements, ast::function_definition * in_function, bool in_loop)
{
using statement_visitor::apply;
ast::location apply(ast::expression_ptr const & node)
for (auto & statement : statements->statements)
{
return ast::get_location(*node);
}
ast::location apply(ast::assignment const & node)
{
return node.location;
}
ast::location apply(ast::variable_declaration const & node)
{
return node.location;
}
ast::location apply(ast::if_chain & node)
{
bool first = true;
for (auto & block : node.blocks)
if (auto if_chain = std::get_if<ast::if_chain>(statement.get()))
{
block.location = apply(*block.statements);
if (first)
node.location = block.location;
else
node.location = ast::merge(node.location, block.location);
first = false;
for (auto const & block : if_chain->blocks)
{
validate(block.statements, in_function, in_loop);
}
}
return node.location;
}
ast::location apply(ast::while_block & node)
{
return node.location = ast::merge(node.prelude_location, apply(*node.statements));
}
ast::location apply(ast::break_statement & node)
{
return node.location;
}
ast::location apply(ast::continue_statement & node)
{
return node.location;
}
ast::location apply(ast::function_definition & node)
{
return node.location = ast::merge(node.prelude_location, apply(*node.statements));
}
ast::location apply(ast::foreign_function_declaration & node)
{
return node.location = node.prelude_location;
}
ast::location apply(ast::return_statement const & node)
{
return node.location;
}
ast::location apply(ast::struct_definition & node)
{
node.location = node.prelude_location;
for (auto const & field : node.fields)
node.location = ast::merge(node.location, field.location);
return node.location;
}
ast::location apply(ast::statement_list & list)
{
ast::location result;
bool first = true;
for (auto & statement : list.statements)
else if (auto while_block = std::get_if<ast::while_block>(statement.get()))
{
auto statement_location = apply(*statement);
if (first)
result = statement_location;
else
result = ast::merge(result, statement_location);
first = false;
validate(while_block->statements, in_function, true);
}
else if (auto function_definition = std::get_if<ast::function_definition>(statement.get()))
{
validate(function_definition->statements, function_definition, in_loop);
}
else if (auto return_statement = std::get_if<ast::return_statement>(statement.get()))
{
if (!in_function)
throw parse_error("Return statement outside of function scope", return_statement->location);
return_statement->node = in_function;
}
else if (auto break_statement = std::get_if<ast::break_statement>(statement.get()))
{
if (!in_loop)
throw parse_error("Break without an enclosing loop", break_statement->location);
}
else if (auto continue_statement = std::get_if<ast::continue_statement>(statement.get()))
{
if (!in_loop)
throw parse_error("Continue without an enclosing loop", continue_statement->location);
}
return result;
}
};
}
ast::statement_list_ptr finalize(indented_statement_list statements)
{
ast::statement_list_ptr result = std::make_unique<ast::statement_list>();
using stack_entry = std::variant<ast::statement_list *, ast::struct_definition *>;
std::vector<stack_entry> stack;
stack.push_back(result.get());
std::size_t current_indent = 0;
std::vector<ast::function_definition *> function_stack;
std::vector<ast::statement_list *> loop_stack;
auto current_statement_list = [&](ast::location const & location) -> ast::statement_list *
{
if (stack.empty())
throw internal_error("Empty finilization stack");
if (auto list = std::get_if<ast::statement_list *>(&stack.back()))
return *list;
throw parse_error("Unexpected statement inside struct definition", location);
};
auto current_struct_definition = [&](ast::location const & location) -> ast::struct_definition *
{
if (stack.empty())
throw internal_error("Empty finilization stack");
if (auto list = std::get_if<ast::struct_definition *>(&stack.back()))
return *list;
throw parse_error("Unexpected statement outside struct definition", location);
};
for (auto & statement : statements.statements)
{
auto location = ast::get_location(*statement.statement);
if (statement.indentation > current_indent)
throw parse_error("Unexpected indent", location);
while (statement.indentation < current_indent)
{
if (stack.empty())
throw ast::invalid_ast_error("Unexpected empty indent stack", ast::get_location(*statement.statement));
if (!function_stack.empty() && std::holds_alternative<ast::statement_list *>(stack.back()) && function_stack.back()->statements.get() == std::get<ast::statement_list *>(stack.back()))
function_stack.pop_back();
if (!loop_stack.empty() && std::holds_alternative<ast::statement_list *>(stack.back()) && loop_stack.back() == std::get<ast::statement_list *>(stack.back()))
loop_stack.pop_back();
stack.pop_back();
--current_indent;
}
// Now statement.indentation == current_indent
ast::statement_list * list = nullptr;
if (auto if_block = std::get_if<ast::if_block>(statement.statement.get()))
{
ast::if_chain chain;
chain.location = if_block->location;
chain.blocks.push_back({.condition = std::move(if_block->condition), .statements = std::make_unique<ast::statement_list>(), .prelude_location = if_block->location});
list = chain.blocks.back().statements.get();
current_statement_list(location)->statements.push_back(std::make_unique<ast::statement>(std::move(chain)));
}
else if (auto else_block = std::get_if<ast::else_block>(statement.statement.get()))
{
if (current_statement_list(location)->statements.empty())
throw parse_error("Unexpected else block", location);
auto chain = std::get_if<ast::if_chain>(current_statement_list(location)->statements.back().get());
if (!chain || chain->blocks.empty() || !chain->blocks.back().condition)
throw parse_error("Unexpected else block", location);
chain->blocks.push_back({.condition = nullptr, .statements = std::make_unique<ast::statement_list>(), .prelude_location = else_block->location});
list = chain->blocks.back().statements.get();
}
else if (auto else_if_block = std::get_if<ast::else_if_block>(statement.statement.get()))
{
if (current_statement_list(location)->statements.empty())
throw parse_error("Unexpected else if block", location);
auto chain = std::get_if<ast::if_chain>(current_statement_list(location)->statements.back().get());
if (!chain || chain->blocks.empty() || !chain->blocks.back().condition)
throw parse_error("Unexpected else if block", location);
chain->blocks.push_back({.condition = std::move(else_if_block->condition), .statements = std::make_unique<ast::statement_list>(), .prelude_location = else_if_block->location});
list = chain->blocks.back().statements.get();
}
else if (auto while_block = std::get_if<ast::while_block>(statement.statement.get()))
{
while_block->statements = std::make_unique<ast::statement_list>();
list = while_block->statements.get();
current_statement_list(location)->statements.push_back(std::make_unique<ast::statement>(std::move(*while_block)));
loop_stack.push_back(list);
}
else if (auto function_definition = std::get_if<ast::function_definition>(statement.statement.get()))
{
function_definition->statements = std::make_unique<ast::statement_list>();
auto statement = std::make_unique<ast::statement>(std::move(*function_definition));
auto function_definition_ptr = std::get_if<ast::function_definition>(statement.get());
current_statement_list(location)->statements.push_back(std::move(statement));
list = function_definition_ptr->statements.get();
function_stack.push_back(function_definition_ptr);
}
else if (auto field_definition = std::get_if<ast::field_definition>(statement.statement.get()))
{
auto current = current_struct_definition(location);
for (auto const & field : current->fields)
if (field.name == field_definition->name)
throw parse_error("Duplicate field definition: \"" + field.name + "\"", field.location);
current->fields.push_back(*field_definition);
}
else if (auto struct_definition = std::get_if<ast::struct_definition>(statement.statement.get()))
{
current_statement_list(location)->statements.push_back(std::make_unique<ast::statement>(std::move(*struct_definition)));
stack.push_back(std::get_if<ast::struct_definition>(current_statement_list(location)->statements.back().get()));
++current_indent;
}
else if (auto return_statement = std::get_if<ast::return_statement>(statement.statement.get()))
{
if (function_stack.empty())
throw parse_error("Return statement outside of function scope", return_statement->location);
return_statement->node = function_stack.back();
current_statement_list(location)->statements.push_back(std::make_unique<ast::statement>(std::move(*return_statement)));
}
else if (auto expression_ptr = std::get_if<ast::expression_ptr>(statement.statement.get()))
{
current_statement_list(location)->statements.push_back(std::make_unique<ast::statement>(std::move(*expression_ptr)));
}
else if (auto assignment = std::get_if<ast::assignment>(statement.statement.get()))
{
current_statement_list(location)->statements.push_back(std::make_unique<ast::statement>(std::move(*assignment)));
}
else if (auto variable_declaration = std::get_if<ast::variable_declaration>(statement.statement.get()))
{
current_statement_list(location)->statements.push_back(std::make_unique<ast::statement>(std::move(*variable_declaration)));
}
else if (auto foreign_function_declaration = std::get_if<ast::foreign_function_declaration>(statement.statement.get()))
{
current_statement_list(location)->statements.push_back(std::make_unique<ast::statement>(std::move(*foreign_function_declaration)));
}
else if (auto break_statement = std::get_if<ast::break_statement>(statement.statement.get()))
{
if (loop_stack.empty())
throw parse_error("Break without an enclosing loop", break_statement->location);
current_statement_list(location)->statements.push_back(std::make_unique<ast::statement>(std::move(*break_statement)));
}
else if (auto continue_statement = std::get_if<ast::continue_statement>(statement.statement.get()))
{
if (loop_stack.empty())
throw parse_error("Continue without an enclosing loop", continue_statement->location);
current_statement_list(location)->statements.push_back(std::make_unique<ast::statement>(std::move(*continue_statement)));
}
else
{
throw ast::invalid_ast_error(std::format("Unknown pre-statement \"{}\"", std::visit([](auto const & statement){ return typeid(statement).name(); }, *statement.statement)), location);
}
if (list)
{
stack.push_back(list);
++current_indent;
}
}
fill_location_visitor{}.apply(*result);
}
return result;
ast::statement_list_ptr finalize(ast::statement_list_ptr statements)
{
validate(statements, nullptr, false);
return statements;
}
}

View file

@ -1,6 +1,6 @@
#include <pslang/parser/parser.hpp>
#include <pslang/parser/context.hpp>
#include <pslang/parser/indented_statement.hpp>
#include <pslang/parser/finalize.hpp>
#include <pslang/ast/location.hpp>
#include "gen_parser.hpp"
#include "gen_lexer.hpp"
@ -15,7 +15,7 @@ namespace pslang::parser
throw std::system_error(std::make_error_code(static_cast<std::errc>(errno)));
ast::location location{.begin = {.filename = path}, .end = {.filename = path}};
indented_statement_list statements;
ast::statement_list_ptr statements;
context ctx{location, statements};
bison::parser parser(ctx);

301
spec.txt
View file

@ -36,41 +36,41 @@ Function types:
======== LITERALS ========
Literals:
56b -> i8
42ub -> u8
456s -> i16
456us -> u16
98765 -> i32
98765u -> u32
123l -> i64
123ul -> u64
3.14h -> f16
3.14 -> f32
3.14d -> f64
'a' -> u8 (ascii only?)
'猫'u -> u32
56b -> i8
42ub -> u8
456s -> i16
456us -> u16
98765 -> i32
98765u -> u32
123l -> i64
123ul -> u64
3.14h -> f16
3.14 -> f32
3.14d -> f64
'a' -> u8 (ascii only?)
'猫'u -> u32
// TODO: string literals? fixed-size arrays? built-in spans? Probably built-in spans (defined in prelude.psl)
"hello, world" -> utf-8 string
"здарова, братки"u -> utf-32 string
"hello, world" -> utf-8 string
"здарова, братки"u -> utf-32 string
======== VARIABLES ========
Variable declaration:
const x = ... compile-time value, type inferred
const x: T = ... compile-time value of type T
let x = ... immutable value, type inferred
let x: T = ... immutable value of type T
mut x = ... mutable, ...
mut x: T = ...
const x = ... compile-time value, type inferred
const x: T = ... compile-time value of type T
let x = ... immutable value, type inferred
let x: T = ... immutable value of type T
mut x = ... mutable, ...
mut x: T = ...
Array declaration:
let arr: i32[4] = [12, 15, 65, 42]
let arr = [2, 5, 6] // size and type inferred as i32[3]
let arr: i32[0] = [] // need special empty array literal, type cannot be inferred
let arr: i32[4] = [12, 15, 65, 42]
let arr = [2, 5, 6] // size and type inferred as i32[3]
let arr: i32[0] = [] // need special empty array literal, type cannot be inferred
Null pointer literal:
let p: u32* = null // special like empty array literal, type cannot be inferred
let p: u32* = null // special like empty array literal, type cannot be inferred
Variables must always be initialized. // TODO: really? What about arrays? Maybe need special syntax for zero-initialization or mass-initialization. Alternative: default to zero-initialization
Const variables must be initialized with a const expression (any expression that doesn't include non-const values).
@ -78,162 +78,176 @@ Const variables must be initialized with a const expression (any expression that
======== OPERATORS ========
Logical (only bool type):
!x
x & y
x | y
x && y // short-circuit
x || y // short-circuit
x ^ y
!x
x & y
x | y
x && y // short-circuit
x || y // short-circuit
x ^ y
Equality (all built-in types, all pointer types, all array/struct types, only same type unless integers):
x == y
x != y
x == y
x != y
Comparison (all built-in types, all pointer types, all array/struct types, only same type unless integers):
x < y
x > y
x <= y
x >= y
x < y
x > y
x <= y
x >= y
Bitwise (integer types, only same type):
!x
x & y
x | y
x && y // short-circuit
x || y // short-circuit
x ^ y
!x
x & y
x | y
x && y // short-circuit
x || y // short-circuit
x ^ y
Bitwise shift (any integer + any unsigned integer type):
x >> y
x << y
x >> y
x << y
Arithmetic (only same integer/floating-point type):
-x
x + y
x * y
x / y
x % y
-x
x + y
x * y
x / y
x % y
Pointer arithmetic (any pointer type + any integer type):
p + x
p - x
p - q // returns i64
p + x
p - x
p - q // returns i64
Pointer arithmetic works element-wise (like C or C++), i.e. p + n advances by n * sizeof(T) when typeof(p) is *T
Casting:
x as u32 // always explicit, no implicit casts allowed
x as u32 // always explicit, no implicit casts allowed
The only implicit casting allowed is T mut* -> T* (maybe?)
Any integer/floating-point types can be cast to each other.
Any pointer types can be cast to each other // TODO: alignment? UB or safe fallback? Probably UB.
Ternary if operator:
if condition then true_value else false_value
if condition then true_value else false_value
Address:
&x // returns *T, fails if x is a const variable
&mut x // returns *mut T, fails if x is non-mut variable
&x // returns *T, fails if x is a const variable
&mut x // returns *mut T, fails if x is non-mut variable
Assignment:
x = 15 // requires x to be a mut variable
*p = 15 // p must be a pointer to mut
x = 15 // requires x to be a mut variable
*p = 15 // p must be a pointer to mut
Special built-ins:
typeof(value)
sizeof(type)
sizeof(value) // same as sizeof(typeof(value))
alignof(type)
alignof(value) // same as alignof(typeof(value))
offsetof(struct type, field)
offsetof(field access expression)
typeof(value)
sizeof(type)
sizeof(value) // same as sizeof(typeof(value))
alignof(type)
alignof(value) // same as alignof(typeof(value))
offsetof(struct type, field)
offsetof(field access expression)
======== FLOW CONTROL ========
Conditionals:
if condition:
statements
else if condition:
statements
else:
statements
if condition {
statements
} else if condition {
statements
} else {
statements
}
While loop:
while condition:
statements
if x:
break
if y:
continue
while condition {
statements
if x {
break
}
if y {
continue
}
}
Iterator interface:
get(it) returns the currently pointed-to value
get_ref(it) returns the pointer to the currently pointed-to value
next(it) returns the next iterator
get(it) returns the currently pointed-to value
get_ref(it) returns the pointer to the currently pointed-to value
next(it) returns the next iterator
Range interface:
begin(range) returns the begin iterator
end(range) returns the end iterator
begin(range) returns the begin iterator
end(range) returns the end iterator
For loop:
Operates only on ranges.
for x in range(10):
do_something(x)
Operates only on ranges.
for x in range(10) {
do_something(x)
}
i is immutable within the loop body.
i is immutable within the loop body.
Modifiable ranges use special syntax for pointers to elements:
Modifiable ranges use special syntax for pointers to elements:
for &x in array:
*x += 1
for &x in array {
*x += 1
}
The loop is equivalent to
The loop is equivalent to
mut begin = begin(range)
let end = end(range)
while begin != end:
let x = get(begin) // or get_ref(x) for pointer loop
statements
begin = next(begin)
mut it = begin(range)
let end = end(range)
while it != end {
let x = get(it) // or get_ref(x) for pointer loop
statements
it = next(it)
}
The prelude contains an implementation of range interface for built-in arrays.
The prelude contains an implementation of range interface for built-in arrays.
======== STRUCTS ========
Struct types:
struct rect:
width: u32
height: u32
struct rect {
width: u32
height: u32
}
Creating a struct value:
let x = rect(10u, 20u)
let y = rect(width = 10u, height = 20u) // named function arguments in general?
let x = rect(10u, 20u)
let y = rect(width = 10u, height = 20u) // named function arguments in general?
Struct field access:
let r = rect(1u, 2u)
let x = r.width
let p = &r
let y = p.height // field access through pointer is the same
let r = rect(1u, 2u)
let x = r.width
let p = &r
let y = p.height // field access through pointer is the same
// TODO: inner struct functions maybe? to act as namespace/module containers
======== FUNCTIONS ========
Function definition:
func foo(x: i32, y: i32) -> i32:
return x * y
func foo(x: i32, y: i32) -> i32 {
return x * y
}
func bar(x: f32): // deduced return type unit
print(x)
func bar(x: f32) { // deduced return type unit
print(x)
}
Function arguments are automatically immutable (as if declared with let).
Function arguments are immutable by default (as if declared with let) unless declared with mut:
// External function: name taken literally as `powf`
// and C calling convention assumed
foreign func powf(x: f32, y: f32) -> f32 // no implementation
func bar(mut x: u32) -> u32 {
x = (x + 1u)
x = x * x
return x
}
// TODO: mutable function arguments?
// External function: name taken literally as `powf`
// and C calling convention assumed
foreign func powf(x: f32, y: f32) -> f32 // no implementation
// TODO: function overloading? Probably requires selecting a specific overload using `as` operator to save to a value (but not on call site)
// TODO: alternative - Rust-like traits, aka parametric polymorphism?
@ -265,12 +279,14 @@ Types are also considered to be values. The keyword `type` denotes the type of a
I.e. `typeof(16) == i32` and `typeof(i32) == type`. Incidentally, `typeof(type) == type` as well; there are no type kinds or etc.
`type` can be used in any place where a type is required (variable types, function arguments, function return value, etc).
E.g.
func foo(x: type) -> type:
return x[4] // type of arrays of 4 elements of type x
func foo(x: type) -> type {
return x[4] // type of arrays of 4 elements of type x
}
let y: type = u32
if foo(y) == u32[4]:
do_smth()
let y: type = u32
if foo(y) == u32[4] {
do_smth()
}
======== CONST EXPRESSIONS ========
@ -283,12 +299,14 @@ E.g.
// Functions returning functions/structs
// Syntactic sugar for common cases
// Figure out: max(a,b) - how to deduce type parameters? How does it play with overloading?
// func max(t: type):
// return func(x : t, y : t):
// func max(t: type) {
// return func(x : t, y : t) {
// if x > y:
// return x
// else:
// return y
// }
// }
======== PRELUDE ========
@ -296,31 +314,32 @@ Prelude is a special source file implicitly included in any project (unless expl
It contains:
An array_view template struct:
An array_view template struct:
struct array_view<t: type>:
size: u64
data: t*
struct array_view<t: type> {
size: u64
data: t*
}
A specialization for strings:
A specialization for strings:
const string_view = array_view<u8>
const string_view = array_view<u8>
(String literals compile into string_view objects.)
(String literals compile into string_view objects.)
Range interface for built-in arrays and for array_view.
Range interface for built-in arrays and for array_view.
Numeric ranges with signatures
Numeric ranges with signatures
range(end) // begin implicitly zero
range(begin, end) // step implicitly one
range(begin, end, step)
that allow iteration like
range(end) // begin implicitly zero
range(begin, end) // step implicitly one
range(begin, end, step)
that allow iteration like
for i in range(10):
for i in range(5u, 10u):
for i in range(1.0, 10.0, 0.5):
for i in range(10) { ... }
for i in range(5u, 10u) { ... }
for i in range(1.0, 10.0, 0.5) { ... }
======== MODULES AND IMPORTS ========