Code Monkey home page Code Monkey logo

penne's Introduction

Penne logo

The Penne Programming Language

Latest Crate License Stars GitHub repo size Lines of code (Rust) Lines of code (Penne)

Build Release Tests Coverage Issues

Penne is an esoteric programming language that imagines a world where, instead of being ostracized for leading to so-called "spaghetti code", the humble goto statement became the dominant method of control flow, surpassing for loops and switch statements, and ultimately obviating the need for the invention of RAII and object-oriented programming in general. By applying modern sensibilities to the use of the goto statement instead of banishing it altogether, Penne seeks to bring about a rennaissance of pasta-oriented programming.

A quick taste

Penne's general aesthetic is inspired by modern programming languages (in particular Rust), with the notable exception of labels and the goto statement, which are (at least syntactically) taken from C, and the loop statement.

// Calculate the number of Collatz steps needed to reach 1.
// The Collatz conjecture states that this function always terminates.
fn determine_collatz_number(start: i32) -> i32
{
	var x = start;
	var steps = 0;
	{
		if x == 1
			goto return;
		do_collatz_step(&x);
		steps = steps + 1;
		loop;
	}

	return: steps
}

// If x is even, divide it by 2. Otherwise calculate 3 * x + 1.
// Do this without division or modulo operators (for demonstrative purposes).
fn do_collatz_step(x: &i32)
{
	var y = x;
	{
		if y == 0
		{
			if y + y == x
			{
				x = y;
				goto end;
			}
			y = y + 1;
			loop;
		}
		else if y == 1
		{
			x = 3 * x + 1;
			goto end;
		}
		y = y - 2;
		loop;
	}
	end:
}

Usage

The compiler uses LLVM as its backend. It requires LLVM version 6.0 or newer to be installed.

# Install it (requires Rust 1.60.0 or newer):
cargo install penne

# Compile a source file:
penne examples/addition.pn

# Or run it directly (using lli):
penne run examples/addition.pn
# Output: 10

There are precompiled binaries for Ubuntu 20.04.

Language features

A brief overview of the more unique language features of Penne:

Scoped goto statements

In Penne, goto is a local forward-only jump. This is achieved by giving labels a reverse scope: similar to how variables cannot be referenced before they are declared, labels cannot be jumped to after they are declared.

fn foo() -> i32
{
	var x = 0;
	goto end;
	x = 10; // This line is not executed.
	end:
	x = x + 1;
	return: x
}

Scoped loop statements

The only way to jump back is with the loop statement.

fn foo() -> i32
{
	var x = 0;

	{
		x = x + 1;
		loop;
	}

	// This line is never reached.
	return: x
}

Views

Function arguments such as arrays and structs are passed as a view. For arrays this means an array view (or "slice") is created and passed into the function. Array views remember the length of their array, which can be accessed with the length operation |x|.

fn foo()
{
	var data: [4]i32 = [1, 2, 3, 4];
	var total = sum(data);
}

fn sum(x: []i32) -> i32
{
	var total = 0;
	var i = 0;
	{
		if i == |x|
			goto return;
		total = total + x[i];
		i = i + 1;
		loop;
	}
	return: total
}

Reference pointers

Reference pointers allow a function to modify its arguments, but require the caller to explicitly pass in an address.

fn foo()
{
	var data: [4]i32 = [1, 2, 3, 4];
	set_to_zero(&data);
}

fn set_to_zero(x: &[]i32)
{
	var i = 0;
	{
		if i == |x|
			goto end;
		x[i] = 0;
		i = i + 1;
		loop;
	}
	end:
}

Unlike pointers in most other languages, reference pointers (including pointers to pointers) automatically dereference to their base type, which is any type that isn't a reference pointer.

	var x: i32 = 17;
	var a: &i32 = &x;
	var b: &&i32 = &&a;
	var y: i32 = b;
	b = 30;
	// Now x == 30 and y == 17.

To change which value a reference pointer points to, you need to explicitly modify the address.

	var x: i32 = 17;
	var y: i32 = 30;
	var z: i32 = 88;
	var a: &i32 = &x;
	&a = &y;
	// Now a points to y instead of x.
	var b: &i32 = &z;
	&a = &b;
	// Now a and b both point to z.

Structs and words

Like arrays, structural types declared with the struct keyword are implicitly passed as a view and cannot be used as the return value of a function. Fixed size structures, declared with word8, word16, word32, word64 or word128, are passed by value.

Imports

The import keyword is used to import all function signatures, structures and constants marked pub from a source file into the destination file. Imports are themselves not public and hence are not re-imported.

Interoperability with C

Functions marked extern use the C ABI, which means it is possible (though not necessarily safe) to call them from C code compiled by LLVM. Conversely, declaring a function header such as

    extern fn foo(buffer: []u8, length: usize);

allows you to call a C function from Penne code. Interacting with other programming languages that utilize or support the C ABI, such as C++, Rust, Zig or WebAssembly, is also possible.

Only array views, pointers and the primitive types i8, i16, i32, i64, u8, u16, u32, u64 and usize are allowed in the signature of an extern function. Array views in extern functions correspond to (const) pointers in C, do not have a length (|x|) and must not be null. In a future version of Penne, pointers will also be assumed to be non-null and an "optional" type must be used to mark nullable pointers.

Structures and constants can also be declared extern, but as of v0.3.0 this has no effect.

Non-features

Penne is an esoteric language, not a general purpose or systems programming language. Certain modern features that you or I may think essential for a good programming language in 2023 to have, are omitted. This is either because including them would contradict the premise of Penne (see above) or to simplify its implementation. As such, the following are decidedly not features of Penne:

  • classes;
  • generics;
  • iterators;
  • support for pointers larger than 64 bits;
  • a string type guaranteed to be UTF-8;
  • memory safety of any kind.

Documentation

A detailed reference of language features and error codes is available on the website.

Contributing

Penne and its compiler are still in development, and many language features (enums, modules) are yet to be implemented. However it is my intention for any gaps in functionality to raise a proper error message until they are implemented. If you encounter a compiler segfault or panic, or if the compiler generates invalid LLVM IR, opening an issue with a minimal reproducible example would be much appreciated.

License

This library was created by Sander in 't Veld. It is made available to you under the MIT License, as specified in LICENSE.txt.

The software is provided "as is", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software.

penne's People

Contributors

sliv9 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

penne's Issues

One small concern with the dereferencing logic

I really like the dereferencing logic. It's good for refactors.

For example in C, when you have a reference x and supply it to a function, you would do f(x).
But if you have a struct reference s and want to supply the field x by reference you have to explicitly deref and take the field (normally both at once using the arrow operator) and then take the reference of it like f(&s->x)

In Penne it would like this (if there were structs already):

f(&x);
f(&s.x);

Some other example:

fn test(some_value: i32)
{
    // dozens of uses of some_value like this:
    f(some_value)
}

Now I realize my function has to take the value by reference for some reason:

fn test(some_value: &i32)
{
    // dozens of uses of some_value like this:
    f(some_value)
}

Still works the same way.

Now my concern: The refactored function will implicitly dereference some_value dozens of times. This might cause an unnecessary overhead.

Will the compiler be able to optimize that away?
Or will you need to do it explicitly by defining a function like this:

fn test(some_value: &i32)
{
    var some_value = some_value; // implicitly deref here for performance reasons
    // dozens of uses of some_value like this:
    f(some_value)
}

Allow multi line comments

I'd suggest using pairs of /* and */ like in C, since you already use // for single line comments like C.

Idea: Allow `loop` to take parameters

Blocks will take parameters, for example by specifying them using with before them.

For example now a factorial would look like this:

fn factorial(x: i32)
{
    var result: i32 = 1;
    var i = x;

    {
        if i == 0
            goto return;
        result = result * i;
        i = i - 1
        loop;
    }
    
    return: result
}

When loop takes parameters, it could look like this:

fn factorial(x: i32)
{
    with result: i32 = 1, i = x;
    {
        if i == 0
            goto return;
        loop result * i, i - 1;
    }
    
    return: result
}

(I don't think, these with-parameters should be exposed after the loop, but if return parameters are not exposed, but then we would need other features like #3 to make it useful)

Idea: Labels and goto should be allowed to take parameters

Simple usage to simplify return:

if some_condition
{
    // supplies 1 and 2, will return 2
    goto return 1, 2;
}
else
{
    // supplies 2 and 3, will return 6
    goto return 2, 3;
}

// labels might take multiple parameters
return (x: i32, y: i32):
    x * y

Discontinuity in `if` vs `loop` syntaxes

At the moment, you'd need to scan down to the end of a block to see if it loops, whereas with an if, you can tell right from the start that the following scope can have a possibly of not running, as well as what condition is required to make it do so.

Perhaps instead of putting the loop; at the end of the block, it would be easier to read putting it near the start. Perhaps something like:

fn foo() -> i32
{
	var x = 0;

	loop {
		x = x + 1;
	}

	return: x
}

(example taken from your readme).

Another option is, instead of using loop, you could instead use the word forever as it gives more of an idea as to what happens.

I can understand why not though, as goto statements go at the end of the code to repeat. But then again if that's the case, why not use this syntax and remove loop altogether?

fn foo() -> i32
{
	var x = 0;

	increment: {
		x = x + 1;
		goto increment;
	}

	return: x
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.