How to Build a Macro and Use it


carl@...
 

Hi, 

I'm coming to this project from Jitx. I'm trying to write a macro as an exercise in understanding the environment. I'm using stanza version 0.17

callendorph@aft-gpu3:~/Documents/Jitx/assert$ stanza version
 
             L.B.Stanza Programming Language
 
                    Version 0.17.25
 
Copyright (c) 2016-2019, Patrick Shaobai Li, The Regents of the 
University of California. All Rights Reserved.
 

I want to write an `assert` like macro similar to what you might use in C. 

Here is the source for the test macro. Keep in mind that it doesn't actually do anything yet - I'm just trying to get it to compile and be able to use it in a stanza command: 

https://gist.github.com/callendorph/2d9f93c694f9b0d345ef771d3a868ec6

I think I have been able to get my macro to compile with: 

callendorph@aft-gpu3:~/Documents/Jitx/assert$ stanza compile assert-check.stanza -pkg ./pkgs
callendorph@aft-gpu3:~/Documents/Jitx/assert$ ls -la pkgs
total 36
drwxrwxr-x 2 callendorph callendorph  4096 Oct 10 12:57 .
drwxrwxr-x 3 callendorph callendorph  4096 Oct 10 12:57 ..
-rw-rw-r-- 1 callendorph callendorph 27098 Oct 10 13:08 assert-check.pkg
 
Now - I want to compile a separate test program that uses this macro:

https://gist.github.com/callendorph/a53581e0cf0b9350994c0897d714028f

Here is where things go off the rails: 

callendorph@aft-gpu3:~/Documents/Jitx/assert$ stanza compile test.stanza -o asdf
FATAL ERROR: Could not resolve syntax package assert-check.
  in core/print-stack-trace
    at core/core.stanza:330.14
  in core/fatal
    at core/core.stanza:383.2
  in parser/syntax-package-registry/package-list
    at core/parser/syntax-package-registry.stanza:131.8
  in parser/syntax-package-registry/package-list
    at core/parser/syntax-package-registry.stanza:140.13
  in core/do
    at core/core.stanza:8207.9
... Long stack trace

OK so it seems like it can't find my package because it isn't in the search path. I find that in the `compiler/config.stanza` directory there is the option for a `pkg-dirs` configuration statement. So I try adding that to my `~/.stanza` file: 

install-dir = "/home/callendorph/Documents/stanza"
platform = linux
pkg-dirs = "/home/callendorph/Documents/Jitx/assert/pkgs"

When I try to recompile: 

$ stanza compile test.stanza -o asdf
/home/callendorph/.stanza:3.0: Invalid configuration rule.
I've tried various iterations on the `$HOME/.stanza` file to no avail. 

I also tried putting a symlink in the `$(install-dir)/pkgs` directory - but this still can't find my package: 

callendorph@aft-gpu3:~/Documents/stanza/pkgs$ ln -sf /home/callendorph/Documents/Jitx/assert/pkgs/assert-check.pkg .
callendorph@aft-gpu3:~/Documents/stanza/pkgs$ ls -la assert-check.pkg
lrwxrwxrwx 1 callendorph callendorph 61 Oct 10 13:16 assert-check.pkg -> /home/callendorph/Documents/Jitx/assert/pkgs/assert-check.pkg

This also results in the "FATAL ERROR: Could not resolve syntax package assert-check." message. 

As a last ditch effort, I tried: 

$> stanza compile assert-check.stanza test.stanza -o asdf

But this results in the same problem.

Can someone please help me understand how I get a macro package to compile and be used in a stanza program ? 


 


 

Hi Carl,

The implementation of the macro looks good. To use it you need to build an "extended compiler". Support for loading in macros dynamically is still in the works.

Here is how you can extend Stanza with your syntax.

stanza extend assert-check.stanza -o mystanza

And then you can use your extended Stanza, which will include your new syntax package.

./mystanza compile test.stanza -o asf

Note that, the above extend command builds the compiler in optimized mode. This is slower but it gives you a nicer error message if you have a bug in one of your macros.

Once you're confident in your macro implementation you can add the -optimize flag.

stanza extend assert-check.stanza -o mystanza -optimize

Hope that helps, Patrick


carl@...
 

Hi Patrick, 

Thank you for taking the time to respond. 

I used the `extend` command to create a new stanza implementation: 

callendorph@aft-gpu3:~/Documents/Jitx/assert$ stanza extend assert-check.stanza -o assertx
callendorph@aft-gpu3:~/Documents/Jitx/assert$ ls
assert-check.pkg     assert-check.stanza~  help_msg.txt  Makefile~  test.stanza
assert-check.stanza  assertx               Makefile      pkgs       test.stanza~

The `assertx` command now exists and I can run it (version and help respond as expected). 

Next I tried to compile using this new command as you have outlined: 

callendorph@aft-gpu3:~/Documents/Jitx/assert$ ./assertx compile test.stanza -o asdf
FATAL ERROR: Could not resolve syntax package assert-check.
  in core/print-stack-trace
    at core/core.stanza:330.14
  in core/print-stack-trace
    at core/core.stanza:336.2
  in core/fatal
    at core/core.stanza:383.2
... Long stacktrace continues

I also attempted to compile with the `-optimize` flag just to try something else but I get the same result. 
 
So I'm not sure what to try next. Do I perhaps have a syntax error in my test program? Do I need to import the macro or added it to some list when I compile with `stanza` ? Any ideas? 

 



 

Ah, I should have read the code more carefully.

Try this:

The construct:

defsyntax assert :
  ...

means that your syntax package is called assert.

So in your test program try using this directive:

#use-added-syntax(assert)

Patrick


carl@...
 

Great, I figured I was doing something wrong. 

Now when I run: 

callendorph@aft-gpu3:~/Documents/Jitx/assert$ ./assertx compile test.stanza -o asdf
Assert Check
check-expr = true
FATAL ERROR: No appropriate branch for arguments of type (True).
  in core/print-stack-trace
    at core/core.stanza:330.14
  in core/print-stack-trace
    at core/core.stanza:336.2
  in core/fatal
    at core/core.stanza:383.2
  in core/no-branch-error
    at core/core.stanza:260.2
  in assert-check
    at assert-check.stanza:12.60
... Long Stacktrace continues



I get something that I can work with. Alright, now the fun begins. :) 

Thank you!


 

The error is in the call to name(...).

Since the expression you passed in was true. This is not a Symbol, and hence you cannot call name on it.

Here is one possible fix:

val printable-name = match(unwrap-token(check-expr)) :
  (s:Symbol) : name(s)
  (s) : to-string("Literal %~" % [s])

val format-string = to-string("ASSERT: %_ = %%~" % [printable-name])

Patrick


carl@...
 

Hi, 

Thanks for the suggestions - I did indeed have many things that didn't quite do what I wanted.

I have a follow on question that I'm hoping you might help with. Here is the my current `assert-check` code: 

https://gist.github.com/callendorph/65eecc48e41be29bd79f644da55b2f88

I'm not winning any beauty pageants with this implementation. But what I'm struggling to figure out is how to differentiate between symbols that are variables/values in the runtime and operators or other special symbols that are values.

For example, in my test program I now do: 

```
val b = 1
val a = 2
assert( (a > b) )
```

And if I compile the assert macro: 

```
callendorph@aft-gpu3:~/Documents/Jitx/assert$ stanza extend assert-check.stanza -o assertx
callendorph@aft-gpu3:~/Documents/Jitx/assert$ ./assertx compile test.stanza -o asdf |& head -n 20
Symbols[3]: (b < a)
Code: (if not (a < b) : (println (@do "ASSERT: (a < b) = %~" % (@tuple (a < b))) println (@do "LOC: test.stanza:10.13") println (@do "Symbol: b = %~" % (@tuple b)) println (@do "Fail Here")))
callendorph@aft-gpu3:~/Documents/Jitx/assert$ ./asdf
asdf
ASSERT: (a < b) = false
LOC: test.stanza:10.13
Symbol: b = 1
Fail Here
```

This all works great when I just print out symbols like `a` or `b`. But I'm trying to figure out how to differentiate between `a` and the  `<` operator. If I just try to format it - I get a runtime error during `./assertx compile` step:

```
callendorph@aft-gpu3:~/Documents/Jitx/assert$ ./assertx compile test.stanza -o asdf |& head -n 20
Errors occurred during parsing:
  test.stanza:10.18: Syntax Error: Expression expected here.
Symbols[3]: (b < a)
Code: (if not (a < b) : (println (@do "ASSERT: (a < b) = %~" % (@tuple (a < b))) println (@do "LOC: test.stanza:10.13") println (@do "Symbol: < = %~" % (@tuple <)) println (@do "Fail Here")))
```

That makes sense because the `parse-syntax` step (at least as I understand it) is basically parsing and expanding the `form` code into actual excutable statements. It sees `println("Symbol: < = %~" % [<])` and says "thats not right" and bails. 

My best guess at this point is to use a `try/catch` like approach on the `to-string` of the symbol. At first glance that seems inefficient.

How do I differentiate between symbols that are variables from symbols that are not variables?


 

The following might be useful. In general, it is impossible to ask the system whether a symbol corresponds to a legitimate variable or not, because the macroexpansion phase happens (and must happen) before the compiler runs any analysis on the program.

But, the following might be enough to accomplish what you're going after.

defsyntax assert :
  import (exp4, exp!) from core

  ;Assuming that the given form has structure: ($do func arguments ...),
  ;return all the arguments that are a Symbol.
  ;Throw an exception, if the form doesn't have that structure.
  defn identify-argument-symbols (form) -> List<Symbol> :
    match-syntax(form) :
      ($do ?f ?args ...) :
        to-list $
          for arg in unwrap-all(args) filter :
            arg is Symbol
      (_ ...) :
        throw(Exception("Unsupported pattern within assert. Received %_." % [form]))

  ;Parse an assert expression.
  ;The argument must itself be a Stanza expression.
  defrule exp4 = (assert(?check-expr:#exp!)) :
    println("check-expr = %_" % [check-expr])
    println("argument variables are: %," % [identify-argument-symbols(check-expr)])
    "Not yet finished"

The trick is that the argument to the assert is directly parsed as a Stanza expression. And all of the basic comparison operators: e.g. x < y expand into a function call, which is the primitive form ($do less? x y).

So the macro can assume that it has that form, and pull out the names of the variables in the arguments.

If you compile the above with some code that looks like:

assert(x < y)

The compiler prints out:

check-expr = ($do less? x y)
argument variables are: x, y

Is that close to what you're going after?

Thanks for the questions, Patrick


carl@...
 

Ah - very interesting. So I think there are a few things to unpack here: 

  1. The `assert(?check-expr:#exp!)` - So if I understand correctly, this is what is taking the passed expression and then applying the production exp! to it which results in an s-expression representation of the passed `check-expr`. This is a "binder" as you refer to it in the macro docs.
  2. `match-syntax` is new to me - I guess I either missed that in the docs or it isn't specifically discussed. Thanks for the tip. Where is this concept defined? Is this a language primitive? It is very similar to the `match` concept which is seems to be a lostanza primitive.
  3. If I understand this correctly - then your proposal will match basically anything that gets converted into a ($do ...) style s-expression. This would include:
    1. Binary operators like my specific example (a>b). This would also capture things like (a+b) or (a*b)
    2. I think it also gets unary operators, like (not a) -> ($do not a) ? 
    3. It would also include things like somefunc(a,b) -> ($do somefunc a b)
    4. It would not handle ($prim f a b) - (I'm not sure what is considered a primitive function as spec'd in your paper section 3.2) - but there is no reason I couldn't also handle it here in this match-syntax. 
So the macro can assume that it has that form, and pull out the names of the variables in the arguments.

Yes - so there is a standard form here that applies to a lot of the type of expressions that I would want to handle. That makes life a lot easier. 

Great - Thank you!


 

Hi Carl!

The assert(?check-expr:#exp!) - So if I understand correctly, this is what is taking the passed expression and then applying the production exp! to it which results in an s-expression representation of the passed check-expr. This is a "binder" as you refer to it in the macro docs.

That's correct. The implementation of the exp! production already returns the fully-expanded form of the Stanza expression, just like how your assert macro returns the fully-expanded form of the assert expression.

match-syntax is new to me - I guess I either missed that in the docs or it isn't specifically discussed. Thanks for the tip. Where is this concept defined? Is this a language primitive? It is very similar to the match concept which is seems to be a lostanza primitive.

The docs for the macro system are under development cause it's considered an advanced feature. The match-syntax expression allows you to use the same syntax that you've been using for defrule to check whether a list matches some pattern.

If I understand this correctly - then your proposal will match basically anything that gets converted into a ($do ...) style s-expression. This would include: Binary operators like my specific example (a>b). This would also capture things like (a+b) or (a*b) I think it also gets unary operators, like (not a) -> ($do not a) ? It would also include things like somefunc(a,b) -> ($do somefunc a b) It would not handle ($prim f a b) - (I'm not sure what is considered a primitive function as spec'd in your paper section 3.2) - but there is no reason I couldn't also handle it here in this match-syntax.

That's correct. So this approach is decently robust. Most syntactical shorthands in Stanza turn into some sort of function call, so this works on many things. Here's another form that it would work on as well:

assert(myflags[flag1])

The above expands into: ($do get myflags flag1)


carl@...
 

OK - so I'm really close :)

With your help understanding the expression production rule concept and then using the `nested` concept as part of the `fill-template` I was able to get something that worked. Here is my latest: 

https://gist.github.com/callendorph/f69cdb54f24650be7a153a9bcd8a8286

In LINUX  - I can do asserts and it prints out all the information I want: 
callendorph@aft-gpu3:~/Documents/Jitx/assert$ ./asdf
asdf
ASSERT: ($do less? a b) = false
Symbols: ["a" => 2 "b" => 1]
Fail Here
ASSERT: ($do less? a b) = false
MSG: "with message"
Symbols: ["a" => 2 "b" => 1]
Fail Here
ASSERT: ($do less? a b) = false
MSG: "Some Other Msg: 1"
Symbols: ["a" => 2 "b" => 1]
Fail Here

I'm now trying to get this to compile and build in windows 10 - I'm using stanza version 0.17.22. 

PS C:\Users\callendorph> $env:Path += ';E:\mingw64\x86_64-5.4.0-release-posix-seh-rt_v5-rev0\mingw64\bin'
PS C:\Users\callendorph> gcc --version
gcc.exe (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 5.4.0
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
This is the version of gcc I'm currently trying. I've tried some other versions too. 

PS C:\Users\callendorph\.jitx\1.1.2-rc.3> .\stanza\stanza.exe extend .\assert-check.stanza -o assertx.exe
This seems to build the extended stanza version fine - I get an `assertx.exe` file. 

PS C:\Users\callendorph\.jitx\1.1.2-rc.3> .\assertx.exe compile test.stanza -o asdf
check-expr = ($do less? a b)
msg =
Found Args: (a b)
FATAL ERROR: Instruction    long: [rsp + -72] = F6 does not satisfy restrictions.
  in core/print-stack-trace
    at core/core.stanza:330.14
  in core/print-stack-trace
    at core/core.stanza:336.2
  in core/fatal
    at core/core.stanza:383.2
... Long stack trace continues

Full listing here with verbose flag: 

https://gist.github.com/callendorph/9acb8a7affd21ba2b846715fa7d52eca

It seems to be erroring out before it even calls GCC so I don't think this is related to what version of GCC I'm using.

I then backed up and tried compiling a simpler program with my new extended stanza that doesn't reference my new assert macro at all: 

defpackage mypkg :
  import core
 
defn main () :
     println("asdf")
     
main()

This also throws the same error. If I compile that simple program with just `stanza.exe` - it compiles and runs as expected. 

I checked the `.stanza` configuration file and it had: 

```
experimental:
    jit
```

So I commented that out and tried again - still same issue. 

Any ideas why this stanza `extend` operation won't work on Windows but will work in Linux?




 

Ah! Well the good news is that your macro is working! And it's a pretty fancy one too.

The latest issue you're seeing is actually a bug in the Stanza compiler. We actually just ran into that particular error late last week, and just pushed out a fix today in Stanza 0.17.26. This is the bleeding edge though, and it's still going through a cycle of testing before it gets released with the JITX product.

Sorry! If everything looks good after testing, the next release should have the new updates.


carl@...
 

> The latest issue you're seeing is actually a bug in the Stanza compiler. We actually just ran into that particular error late last week, and just pushed out a fix today in Stanza 0.17.26.

Confirmed - I just downloaded the 0.17.26 release and tried in on windows with my setup. The macro compiles and works on windows as expected. Thanks for the tip!

Thank you again for all your help. This has been a really good exercise in understanding stanza. 


 

Glad it all worked Carl!

Also, I do want to say that you dived head-first into the deep end of Stanza. The macro system is one of the more advanced systems.