[go: up one dir, main page]

Skip to content

expression-based language that targets C++17^ (declarative type composition, pattern matching, pointers & references, type predicates, aliases, polymorphic constraints)

License

Notifications You must be signed in to change notification settings

elricmann/esper

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Esper

Esper is a minimal expression-based language that targets C++.

Features

This list is partially a feature matrix of existing features.

  • Non-obtrusive syntax, Esper is related to ML
  • Readable output based on matching semantics
  • Default no-emit mode that delegates output to clang++
  • Expression-level directives for compiler routines

Installation & usage

Minimum versions:

  • rustup: 1.27.0
  • rustc: 1.79.0-nightly
  • clang++: 16.0.6+ (release builds available with LLVM installation)

Clone the repository, build with Cargo (cargo build --release), and run esper --help. Upstream build is tested on Debian with ELF binaries (target x86_64-pc-linux-gnu). On Windows, demangling issues may arise unless the prelude is excluded (WSL/MinGW should be fine).

Range expressions require -std=c++20 when compiling the output C++ source. Optional flags are passed to clang++ as raw arguments.

esper <input> -o <output> -- -std=c++20 -Wall -O3

Quick Overview

The table below compares Esper source programs to the corresponding C++ output (target is EmitDefault). In context, a main function definition is expected since every module is in a separate namespace. Refer to the tests.

Item Esper C++ Description
Type alias
type T = int end
using T = int;

-

Parametric type alias
type T<K> = K end
template<typename K>
using T = K;

Type parameters are required when instantiating.

Reference & pointer types
type T<K> = &K end
type P<U> = **U end
template<typename K>
using T = &K;

template<typename U>
using P = **U;

Type parameters are required when instantiating.

Variant types (tagged unions)
type N = | int | float end

type V<T, K> = | T | K end
using N = std::variant<int, float>;

template<typename T, typename K>
using V = std::variant<T, K>;

-

Optional type
type T = ?int end

type K =
  | ?int
  | ?bool
end

type U = ?| int | bool end
using T = std::optional<int>;

using K = std::variant<std::optional<int>, std::optional<bool>>;

using U = std::optional<std::variant<int, bool>>;

Alias of std::optional. Wraps type expressions to the right. Variant of optionals is not an optional of variant of types.

Mapped types
type M<K, V> = { key: K, value: V } end

type tree<T> = {
  value: T,
  children: vector<tree<T>>
} end
template<typename K, typename V>
struct M {
  using key   = K;
  using value = V;
}

template<typename T>
struct tree {
  using value      = T;
  using children   = std::vector<tree<T>>;
}

Represents structural definitions that can be passed as signatures in polymorphic functions.

Type members
type P<Q> = Q.key. end
template<typename Q>
using P = Q::key;

Overloads the :: syntax when accessing members of type expressions.

Type extensions
@extend(S, string) type R<S> = S end
template<typename S>
using R = std::enable_if_t<
  std::is_same<
    S, std::string>::value
  S
>;

@extend modifier required to apply parametric extended types. Ensures fst extends snd or incurs an error without static assertion.

Type-level function definition
type F =
  |a: int, b: float| ?int end
end
using F = std::function<std::optional<int>(int, int)>;

Return types are parsed as type_expr rule, values are treated as types regardless.

Pattern matching
match n with
| int ->
  print("-> scope");
  print("int: ", _),
| string -> print("string: ", _),
end
std::visit([](auto&& _) {
  using T = std::decay_t<decltype(_)>;
  if constexpr (std::is_same_v<T, int>) {
    print("-> scope");
    print("int: ", _);
  }
  if constexpr (std::is_same_v<T, string>) {
    print("string: ", _);
  }
  }, n);

Non-exhaustive matching, inner values captured as the _symbol. Requiresstd::visitand decaying inner value to base value types. Ideally,get_ifandholds_alternative are performant but not as rigorous.

Typed definitions
let n : int = 0

let p : 0 = 0

let t : | bool | string = true
int n = 0;

decltype(0) p = 0;

std::variant<bool, std::string> t = true;

Expr::TypedSymbol represents type identifiers. Tagged unions are variant entries. Literal types are decltype(T) which is a non-constraint on the rvalue.

Parameterized types (postfix generics)
let lst : vector<int> = []
std::vector<int> lst = {};

-

Typed call expressions (postfix generics)
let lst = vector<int>()
auto lst = std::vector<int>();

-

Variable definitions
let n = 0
auto n = 0;

Initialization of a value is expected. Default type is auto. Multiple definitions as Expr::Let is not allowed.

Function definitions
let _ = || pass end (* since v0.2 *)

let add: int = |a: int, b: int| a + b end

let swap: tuple<int> = |a: int, b: int|
  let tmp = a;
  a = b;
  b = tmp;
  [a, b]
end
auto _() { return; }

int add(int a, int b) { return (a + b); }

std::tuple<int> swap(a: int, b: int) {
  auto tmp = a;
  a = b;
  b = tmp;
  return {a, b};
}

Required return type is the lvalue. Non-inferred parameter types. Last expression is returned. Multiline expressions are delimited with ;.

Struct definition
struct A end

struct B
  c: float,
  d: || c end
end
class A {};

class B {
public:
  float c;
  auto d() { return c; }
};

All symbols are public without @pub. Structs are classes. Methods are fields with function rvalues.

Loops
for a in b [] end
for p in q.r. [] end

for i in 0..10
  print(i)
end

for [a, b] in c
  print(a, b)
end
for (auto a : b) {}
for (auto p : q.r) {}

for (auto i : views::iota(0,10)) {
  print(i);
}

for (auto [a, b] : c) {
  print(a, b);
}

-

Value operators (since v0.2)

~a
&a
&&a
a gt b
a lt b
a gte b
a lte b
a eq b
a neq b
a and b
a or b
a shl b
a shr b
a band b
a bor b
a xor b
a rotl b
a rotr b
~a
&a
&&a
(a > b);
(a < b);
(a >= b);
(a <= b);
(a == b);
(a != b);
(a && b);
(a || b);
(a << b);
(a >> b);
(a & b);
(a | b);
(a ^ b);
__builtin_rotateleft32(a, b);
__builtin_rotateright32(a, b);

-

Postscriptum

Esper is experimental and aims to stay minimal. Matching semantics are not optimized, e.g std::visit for pattern matching is a known performance bottleneck, exclusively using STL Containers (argv is cast from a const char**), all libstdc++ headers are included in the optional prelude, passing by value is exclusive and a wide range of impracticable error handling; with clang++ errors being piped to stdin, PEG's obscure parsing errors (resolved in v0.2) and no type-level resolution of expressions (requires a complete type system and call graph).

License

Copyright © 2024 Elric Neumann. MIT License.

About

expression-based language that targets C++17^ (declarative type composition, pattern matching, pointers & references, type predicates, aliases, polymorphic constraints)

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published