erg-lang / erg

A statically typed language compatible with Python
http://erg-lang.org
Apache License 2.0
2.7k stars 55 forks source link

Add multi-stage programming (macro) system #492

Open mtshiba opened 9 months ago

mtshiba commented 9 months ago

Here I propose to add a multi-stage programming (macro) system to Erg.

Motivation

Erg is compatible with Python's API. In other words, Erg is an alt Python. However, Erg's syntax is far different from Python's. Whereas Python has statements, almost everything in Erg is expressions. if and for are also implemented as functions rather than statements. This makes Erg code look very different from Python code.

if! True:
    do!:
        print! "True"
    do!:
        print! "False"

for! [1, 2, 3], i =>
    print! i

If you try to write an else clause in if, you will need two indentations. for! has the opposite order of i and iterable compared to Python's for i in iterable: ..., which is confusing. I admit that Python's syntax is simpler. However, I feel reluctant to bring the concept of statements into Erg. I want to minimize basic grammar.

Draft plan

Therefore, I will introduce a hygienic macro system to Erg. It is heavily inspired by Scala and defines macros as functions that receive syntax elements and return syntax elements.

Macro = Class *Expr -> Expr 

To introduce macros, two syntaxes will be added: ${} and '{}.

${ x } takes an expression x of type T and returns Expr T. '{ x } takes an expression of type Expr T and returns T.

{Expr; Name} = import "macro/ast"
# Name[Int] <: Expr[Int]

i: Int = 1
expr: Name Int = '{ i }
# expr + 1 # ERROR

i2: Int = ${ expr }
# ${ expr + 1 } # ERROR
# ${ expr } + 1 # OK

assert i == i2

Use these to implement if, for!, import. Note that if this proposal is implemented, the current if function etc. will be moved to the control module instead of built-in.

control = __import__ "control"

@Macro
if|T, U, V|(cond: Expr[Bool], block: Block[T], *'elif' elif: Option[Block[V]] := None, 'else' else: Option[Block[U]] := None:): Expr[T or U or V] =
    ...

@Macro
for!|T|(i: Name[T], 'in' iterable: Expr[Iterable[T]], block: Block[NoneType], 'else' else: Option[Block[NoneType]] := None) -> NoneType =
    if else != None:
        '{
exec = !False
control.for! $iterable, $i =>
    $block
    exec.set! True
if not exec:
    $else
        }
    else:
        '{
control.for! $iterable, $i =>
    $block
        }

@Macro
import(x: Name) = '{ $x = control.import ${ x.as_str() } }

Then we can use them:

if True:
    ...
else:
    ...

for! i in 0..10:
    ...

import control
mtshiba commented 9 months ago

It may be difficult to tell which is which with the syntax ${} and '{}, so it may be better to use the notations quote{} and embed{}.