; ___ __ __ _____ __ __ _ __ ___ __
; / _ | ___ ___ ___ __ _ / / / /_ __ / ___/__ ___ ___ / /_______ ______/ /_(_)__ ___ / //_(_) /_
; / __ |(_-<(_-</ -_) ' \/ _ \/ / // / / /__/ _ \/ _ \(_-</ __/ __/ // / __/ __/ / _ \/ _ \ / ,< / / __/
; /_/ |_/___/___/\__/_/_/_/_.__/_/\_, / \___/\___/_//_/___/\__/_/ \_,_/\__/\__/_/\___/_//_/ /_/|_/_/\__/
; /___/
; ----------------------------------------------------------------------------------------------------------
; NullForth/64 Subproject: x64 Assembly Construction Kit
; ----------------------------------------------------------------------------------------------------------
; file: metaprograms.inc
; version: 1.0
; license: GPL3
; author: conscious@nullforth.org
; purpose: experimental, not for production
; ----------------------------------------------------------------------------------------------------------
; some experiments with metaprograms written in nasm macro language using assembler symbols, just as a proof of concept
; compile with listing and see some results in listing file. there is no use of linking nor running that, of course
; -nasm metaprograms.inc -l metaprograms.lst
; an original fibonacci table generator copied from nasm manual, an inspiration:
%if 0
fibonacci:
%assign i 0
%assign j 1
%rep 100
%if j > 65535
%exitrep
%endif
dw j
%assign k j+i
%assign i j
%assign j k
%endrep
fib_number equ ($−fibonacci)/2
%endif
; well, that was just some ordinary code generator. however, we can do more abstract work with this mechanic
; a metaprogramming debug value tracer :) for tracing metaprogram values into listing file, just comes handy sometimes
%define debug db
; a factorial metaprogram written in nasm's macro language, in a style of metaprograms written in c++ templates
; does only a symbolic computation at assembly time, emits no code, only symbols, declares it's own type/class
%macro factorial 2
; macro parameters:
; 1 new symbol name of a computation
; 2 input argument, a numeric value
; %1: is an assembly symbol for "place", where the computation is performed (symbolic name of a computation is bound to location)
; computation returns %1.result value which is technically a local symbol .result of %1 symbol in assembly,
; and it's semantic for us is cleanly 'a result of computation named %1'. A dark side of anonymous lambda computations...
%defstr %1.type "numeric"
%defstr %1.input "numeric"
%defstr %1.class "computation"
%1:
%assign fact %2
%assign i %2-1
%assign k i
%rep k
%assign fact i*fact
%assign i i-1
%endrep
.result equ fact
%endmacro
; invocation and usage in code:
; here, some computation is done
factorial f,6 ; 2*3*4*5*6 = 0x2d0
; now, a return value can be used back in source:
dq f.result ; "display" a result D0 02 00 00 00 00 00 00 in listing file
; This could be generalized into folds, even factorial is just one of possible folds on binary multiplication
; We need to reinvent symbolic functors with apply to generalize that. Since it's elementary operation is equ evaluator,
; these functors can accept symbols and constants and scalar expressions of them. Scalar in assembler's terminology, of course.
; In the sense of Math theory, we are inside a category of assembler's critical expressions with all that, and both assembly
; symbols and their values are related with an "equ" morphism representing monadic bind (which adds symbol to assembler's symbol
; table, symbol table is a monad and I believe it is provable :).
; Perhaps some theoretic properties could be derived on them. They are quite weak.
; However, preprocessor variables with %assign did a real breakthrough on this in factorial example above.
; They are also critical-limited scalar values, and cannot accept relocatable symbols, but we have already invented
; a "type conversion" from scalar to relocatable symbol with the ".result equ" local symbolic construct
; So now it seems would be possible to write a full calculator on assembler's context stack as a metaprogram, later
; example of meta predicate, tests if a name is present in list of names
; we use that as pure register (or [register]) detector in code generators to distinguish from symbol references and indirects
%macro is_member 3+
; 1 meta computation id
; 2 input, some name
; 3 list of possible names to select from
%defstr %%input %2
%%1:
%rep %0-2
%ifidni %%input,%3
%define %%found
%endif
%endrep
%ifdef %%found
.result equ 1
%else
.result equ 0
%endif
%endmacro
; with this kind of logic predicates, it could be possible to write some unification on symbolic types at compile time,
; (just like prolog does or c++ templates do with type unification)
; --------------
; another experiment: preliminary attempt to construct an expression compiler as metaprogram
%macro PLUS 3
%defstr %1.type "numeric"
%defstr %1.input "numeric numeric"
%defstr %1.class "computation"
%1:
.result equ (%2+%3)
%endmacro
PLUS a,2,3
dq a.result
%macro MULT 3
%defstr %1.type "numeric"
%defstr %1.input "numeric numeric"
%defstr %1.class "computation"
%1:
.result equ (%2*%3)
%endmacro
MULT b,2,3
dq b.result
; an apply functor for binary
%macro APPLY 4
%defstr %1.type "functor"
%define %1.input "operator value value"
%defstr %1.class "computation"
%1:
.first equ %3
.second equ %4
%2 %1.compute,%1.first,%1.second
%1.result equ (%1.compute.result)
%endmacro
; now, we can even compose such computations into expressions:
APPLY c,PLUS,3,4
APPLY d,MULT,2,c.result
dq d.result
; more, we can overload apply for unary and even ternary when needed. here's apply unary
%macro APPLY 3
%defstr %1.type "functor"
%define %1.input "operator value"
%defstr %1.class "computation"
%1:
.first equ %3
%2 %1.compute,%1.first
%1.result equ (%1.compute.result)
%endmacro
; some exemplary unary operator
%macro NEGATIVE 2
%defstr %1.type "numeric"
%defstr %1.input "numeric"
%defstr %1.class "computation"
%1:
.result equ (-%2)
%endmacro
; let's check unary aplication
APPLY x,NEGATIVE,c.result
dq x.result
; another unary operator, this time a practical one in 64bit assembly, a curried ADD
%macro ADD8 2
%defstr %1.type "numeric"
%defstr %1.input "numeric"
%defstr %1.class "computation"
%1:
.result equ (%2+8)
%endmacro
; now, we can try to invent haskell-like currying of any meta binary to meta unary. it's just a partial apply on metas
; however, perhaps a result shall evaluate to a macro invocation, not a symbol value. I am not sure, now.
; let's begin with all curried ADD functions generator
%macro _ADDX 1
%define ADD%1.argument %1
%macro ADD%1 2
%%ADD.%1:
.result equ (%2+ADD%1.argument)
%endmacro
%endmacro
; now, _ADDX serves as a meta generator for curried ADD macros (it is a real metafunction in our macroprogram)
%if 0 ; its still a little broken here...
; declare functors
_ADDX 3
_ADDX 6
; perform some computation with them
APPLY some_cool_curried,ADD3,1
APPLY other_curried,ADD6,some_cool_curried.result
dq other_curried.result
%endif
%macro CURRY 3
%defstr %1.type "functor"
%define %1.input "operator numeric"
%defstr %1.class "computation"
%1:
; I AM WORKING THINKING JUST HERE...
; The ideas are, from an example just above, to
; 1. generate a curried functor from a passed functor macro name
; 2a. apply it to metavalue
; 2b. generate a wrapper macro
%endmacro
; We could also invent a generic fold based on APPLY
; Another already working trick
; variable and greedy macro arguments are a good candidate for effortless cons list implementation
; lets try a folding loop on numbers list first, just for a comparision. every loop is just a special case of fold, either
%macro SUM 3-*
; sum all numbers from %2 up
%defstr %1.type "numeric"
%defstr %1.input "numlist"
%defstr %1.class "computation"
%1:
%assign accumulator %2
%assign j %0-2
%rep j
%rotate 1
%assign i accumulator
%assign accumulator i+%2
%endrep
.result equ accumulator
%endmacro
SUM s0,1,2,3,4,5,6
dq s0.result ; 1+2+3+4+5+6 = 21 = 0x15
; a little test of symbol mix and computation composition
seno equ 3
slama equ 7
trava equ 9
listi equ 2
SUM s1,1,2,seno,3,slama,3,trava,0,listi,f.result
dq s1.result
; now, lets try something on list of symbols. first, a trivial sum
%macro SUMSIZES 2-*
; we expect a size of a <symbol> is an "attribute" predeclared as a symbol in conforming syntax as <symbol>.size
; such pairs can be defined via structured declarators we invented long ago. (They replace a weak and not-so-practical-as-could-be
; legacy struct/endstruct assembly construct without intrinsic support)
; 1 symbolic name of this computation
; 2+ list of symbols, at least one
%1:
%assign i %0-1
%assign accumulator 0
%rep i
%rotate 1
%assign accumulator accumulator + %1.size
%endrep
.result equ accumulator
%endmacro
; example of usage:
first:
.size equ 3
second:
.size equ 4
third:
.size equ (f.result)
SUMSIZES ls,first,second,third
dq ls.result
; now, a little bit generalized sum, sums on any "dot member" of all symbols, like above
%macro SUMMEMBERS 3-*
; 1 symbolic name of this computation
; 2 member id, same to all symbols
; 3+ list of symbols, at least one
%1:
%assign i %0-2
%assign accumulator 0
%define %%member .%2
%rep i
%assign accumulator accumulator + %3 %+ %%member
%rotate 1
%endrep
.result equ accumulator
%endmacro
; same as above, sums ".size" members of symbols from list
SUMMEMBERS ms,size,first,second,third
dq ms.result
; And this is already quite useful, since nasm is not a multipass assembler, it cannot bloat up structures and needs critical
; expressions for every storage reservation and location control
; So this SUMMEMBERS fold above is quite handy to predict table sizes from future of pending assembly by
; summing sizes of structures or data passed to declarators
; let's go deeper. generalize to list fold of APPLY any binary operator. numerics, a concept proof only for today
%macro FOLDAPPLY 5-*
; 1 symbolic name of this computation
; 2 memeber id
; 3 operator macro name
; 4 initial value for fold
; 5+ list of symbols, at least one
%1:
%assign i %0-5
%assign accumulator %4
%define %%member .%2
%rep i
%define %%applied foldapply %+ i
APPLY %1.%%applied,%3,accumulator,%5 %+ %%member
%assign accumulator %1.%%applied.result
%undef %%applied
%rotate 1
%endrep
%1.result equ accumulator
%endmacro
%if 0 ; this is also borked
; FOLDAPPLY z,size,PLUS,0,first,second,third
dq z.result
%endif
; TODO: with this style of coding, invent an evaluator for context expressions (and I mean assembly stack contexts, which nasm
; already has, so we could reimplement an ancient UNIVAC Structured Macro Assembler in NASM macro language at the full strength
; of it's equivalence to Algol or Pascal expression syntax (or C, in today's ideology, it's just the same language still).
; We would need no more compilers, then.
; But the big question behind all this effort above remains:
; Is it possible to implement a Haskell-like language in assembler's preprocessor?