Hi
I'm playing around with Nimrod (which is really fun btw). I'm considering starting a project based on Nimrod (digital design DSL), but first I'm trying to figure out what I can do with the language.
One of the things is to be able to define a "wire" variable which is like "let", but depending on how you look at it either: evaluates its expression every time it is read or: is like a variable that changes its value every time the similar variables in the expression updates. I need another kind of variable which trigger an event every time it is assigned, and I need to be able to have the "wire" subscribe to each such variable in its expression and update the wire's value every time one of the events is triggered.
In Verilog, it's called a "wire". What I'm trying to accomplish would look something like:
var x: int
wire y = x + 2
x = 1
echo y # 3
x = 2
echo y # 4
Is it possible to write a macro that matches statements like "wire y = x + 2" and then allow me to generate the AST I want?
It would also be nice if:
y = 3
Threw a similar compile time error as if "let" had been used for declaring y.
I have a few other approaches I'm going to try, but right now I'm just curious if something like this could work.
For the simple example you provided, something like this would work as all you really need is a closure around the variable x.
var
x: int
y = proc(): int = x + 2
x = 1
echo y() # 3
x = 2
echo y() # 4
# This line results in a compile time error
# y = 3
var x = 0
template y: expr = x + 2
echo y # 2
x = 1
echo y # 3
But keep in mind that this is not like wire in the VHDL sense, because the template is indeed evaluated every time you use it.
Currently, Nimrod doesn't support redefining the assignment operator. When we have this, it would be possible to intercept assignments to produce side-effects.
Thanks for the suggestions!
But the point is to make a DSL, and to be able to build more functionality around the construct. Telling the users of the DSL to "just use a template" is not an option.
A half-way decent option I've found, which gives me the flexibility to make the statement behave as I want, is to use a macro. That is, something like:
macro wire(expr name, expr expression): stmt =
#... implement wire here (for instance register the variable in some kind of event table, and then subscribe to the events of all the wire variables in the expression), then generate the statement "let y = (inital value)", get a pointer to y, and update it behind the scenes?
wire(y, x + 2)
Then in the macro I can implement it as I would like. But this still feels somewhat awkward. I'm declaring a new variable (a dataflow variable) .. why should the syntax be different from declaring a dynamic variable or single-assignment variable?
why should the syntax be different from declaring a dynamic variable or single-assignment variable?
Because that particular syntactic twist is impossible to support with the parsing constraints that are part of Nimrod's design. I can feel your pain though, it's certainly an issue we need to address sooner or later. BTW the following is also possible:
template `:=`(name, val: expr): stmt {.immediate, dirty.} =
var name = val # or: wire(name, val)
foo := 1 * 4
Araq: Ah, thank you! That's what I needed to know. Nimrod is so dynamic it's hard to know what's actually possible and not, and from some of my attempts it seemed like it could actually almost be possible.
Your suggestion is really good enough though!
Presumably, I could perhaps also do:
wire foo := 1 * 4
I notice that if I define a template wire(name: stmt). When I type wire x = 1*4 I get Error: invalid indentation (why btw? what is the parser trying to do here?). But with wire x := 1*4 it seems the whole x := 1*4 bit is passed as an argument to the template (or macro), which I suppose should allow me to do whatever dirty stuff I want to do with it.
Man, I love this language.
It's also good to know we can make assignment operators like that. In verilog for instance, there's an assignment operator <= which delays the update of the variable until the next delta-cycle in the simulation. I. e. if you read it, you always get the value calculated in the previous simulation cycle.
I might build the DSL more exclusively on top of the OOP-part of Nimrod.. I haven't experimented with that part yet, but it might be cleaner that way. Declaring a wire as a value of an object would allow some more control by using setter/getters. But that would perhaps force users to always type bar.foo to access the wire?