I am translating the Ada package Spipat into Nim and have a problem creating generic functions to handle the fixed-length strings:
For example the string conversion function
proc `$`(s: array[char, 1..4]): string
is fine but I need the same for character arrays of lengths 2,3,4,5 and 6 so I attempted to create the corresponding generic function
proc `$`[T](s: array[char, 1..static[T]]): string
which causes the compiler to print: Error: type mismatch
Have I made an error in this generic function prototype or is this use of static[T] not currently supported?
type CharArray[N: static[int]] = array[N, char]
proc `$`[N: static[int]](s: CharArray[N]): string =
...
type CharArray[N: static[int]] = array[1..N, Character]
proc `$`[N: static[int]](s: CharArray[N]): string =
...
compiles fine, I will test it when I get the rest of Spitpat converted.
Lots of legacy Ada and Pascal code use arrays that are 1-based which is annoying. I started by converting PicoProlog from Pascal into Nim and I have it running but the arrays are also 1-based at the moment.
Once I have Spitpat running in Nim I will start renaming the types, variables and functions to be consistent with Nim conventions and then have a go at re-basing all the arrays.
Sorry, that should be Spipat, it is an Ada implementation of Spitbol which is a fast and general pattern-matching method based on the Snobol programing language. I have already implemented it in C++ and started an XL2 version before playing with Nim. I am considering using it as the basis of a general way to select expressions for term-rewriting.
If you or others are interested in Spipat I would be happy to dig-up some useful links.
I am considering using it as the basis of a general way to select expressions for term-rewriting
Interesting, can you elaborate on this idea?
Your excellent term-rewriting efforts are what peaked my interest in Nim. I started working on the same idea in XL2 3 years ago but while it has very good macro support allowing the creation and manipulation of parts of the AST in a similar way to Nim it did not support the concept of macro dispatch on the type of an expression. Rewriting expressions is by far most important functionality I need in a programing language to replace C++. I also considered adding this expression rewriting to C++ using Clang but it is just WAY to difficult to parse so I think it time to start again.
Now the question is how to select the expressions for which rewriting is specified. For my cases these expressions may be rather complex involving several type, operators and functions, e.g. scalar fields, vector fields, tensor fields, inner products, outer products, +, -, mag, sin, pow, tan etc. etc. Lets say I could create a list of the types and operations on those types for which a particular term-rewriting macro is valid, how should this list be specified and how should it be matched against an expression in the code?
One option I considered was matching expressions in the code against regular expressions and dispatch on the match. However regular expressions are a bit messy for this purpose and not really powerful enough so I looked for alternatives and found SNOBOL/Spitbol/SpiPat.
However, I now think it would be better to perform the expression matching on the AST in which case it is necessary to find a way of specifying the type and function/operator combinations in such a way that they can be matched against the AST. Having performed the match should the section of AST be walked in the rewrite macro to put back the expression in the new form or would it be easier to transform the section of matched code directly e.g. using SpiPat? Not sure yet.
At the moment I have many more questions than answers but have a real need for this functionality and a will to see it happen. Also I think that Nim is the best candidate I have seen so far to support this functionality and you and others here are also committed to implementing and supporting this kind of flexibility in Nim which is VERY encouraging having spent years searching for like-minded language designers.
Even though I may not end up using Spipat for this puprose, translating it from Ada into Nim is a good way for me to get into Nim and provide something interesting and potentially useful to Nim users. I really think SNOBOL patters are worth studying and if any others find them useful in Nim I would be happy.
Been there, done that, didn't work; there is a reason why Nim's TR macros look the way they do. ;-)
I tried to map regexes to trees though. The problem here is that the kleene star (and regexes without the kleene star are just too boring to even consider) doesn't map to terms easily: a* matches aaa. But what tree is that? a(a, a)? a(a(a))? And how is this a useful thing to do to optimize matrix operations?
Thanks for the feedback. I also had problems working with the correspondence between regular expressions and the AST. I am very interested in the TR macros in Nim but it is not clear to me if it is currently possible in Nim to perform the following transformation:
Say I have the simple generic type Field which is an array-like entity with additional attributes which are not important for this discussion.
Field is instantiated an a set of primitive types including scalar (effectively float), vector (a 3D vector) and tensor (a 2D 2nd-rank tensor).
Now I have complex Field algebra expressions including operators e.g. *, /, +, -, & (dot-product, will be a UTF8 symbol eventually), ^ (cross-product, will be UTF8) and functions e.g. mag, pow etc.
For example consider the following expression involving s1: scalar, sf1, sf2, sf3: Field[scalar]; v1: vector, vf1, vf2: Field[vector] and tf1: Field[tensor]
var sf1: Field[scalar] = sf2 + s1*sf3*mag(v1 & ((vf1 ^ vf2) & tf1))
which needs to be transformed/optimized to
var sf1: Field[scalar]
for i in 0..sf1.len-1:
sf1[i] = sf2[i] + s1*sf3[i]*mag(v1 & ((vf1[i] ^ vf2[i]) & tf1[i]))
If this is possible then BRILLIANT, if not what functionality would need to be added or is this kind or transformation not realistic with TR macros and I will have to go back to expression templates
If I understand correctly, this is about loop fusion: performing each operation naively would involve a lot more loops. Am I right in understanding the issue?
Now, I am not sure that this can be done in general, but I am using TR macros for similar, albeit more limited, use case. In my linear algebra library I expose a simple interface to users, and use BLAS behind the scenes. Since BLAS allows you to do more than one thing in a single operation (such as y = y + A * x), I have some rewrite rules that match common patterns and rewrite them as a single BLAS operation.
I just tested that this works in very simple cases; I am not actually done writing all the suitable rewrite rules, but for instance the vector operation v1 + 5.3 * v2 is optimized to use a single loop instead of two.
Yes in the example I provided it is simply loop-fusion but for more complex field types other tests/optimizations might be performed like dimension checking (in the sense of mass, length, time etc.) although with concepts I hope to perform these checks at compile-time.
I can see how to use the current TR macros for loop-fusion of a set of operations on a single type e.g. array[float] but it is not clear how I would go about doing the same for expressions involving combinations of types as in the example I provided. Do you know if this might be possible with the current TR macros?
That's already possible with the current TR macros:
macro optScalar{var x = y}(x, y: Field[Scalar]): void =
## now rewrite this as you like, the macro can look at y's structure and the involved types
process(sf2 + s1*sf3*mag(v1 & ((vf1 ^ vf2) & tf1)))
Easy:
proc optScalarDest(dest, src: NimNode): NimNode {.compileTime.} =
.. common implementation
proc optScalarNewTemp(src: NimNode): NimNode {.compileTime.} =
result = newTemp(...)
result = optScalarDest(result, src)
macro optVar{var x = y}(x, y: Field[Scalar]): void =
result = optScalarDest(x, y)
macro optLet{let x = y}(x, y: Field[Scalar]): void =
result = optScalarDest(x, y)
macro optAssign{x = y}(x, y: Field[Scalar]): void =
result = optScalarDest(x, y)
macro optOther{x}(x: Field[Scalar]): void =
result = optScalarNewTemp(x)