Skip to content

Commit

Permalink
modularize, clean up, 0.3.0
Browse files Browse the repository at this point in the history
  • Loading branch information
metagn committed Feb 13, 2021
1 parent aea9ceb commit 2951ef4
Show file tree
Hide file tree
Showing 9 changed files with 166 additions and 154 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@ Would have preferred not using a cache to do this, but for now it should do the

```nim
import applicates
# optional operators:
import applicates/operators
proc map[T](s: seq[T], f: ApplicateArg): seq[T] =
result.newSeq(s.len)
for i in 0..<s.len:
let x = s[i]
result[i] = f.apply(x)
# supported sugar for the above (best I could come up with, might be too much):
# optional operators (couldnt decide on anything):
result[i] = f | x
result[i] = x |< f
result[i] = x |< f # accepts multiple arguments like (1, 2) |< f
result[i] = x |> f # does not do above, and injects x into right hand side
result[i] = \f(x)
result[i] = \x.f
result[i] = f(x) # when experimental callOperator is enabled
Expand All @@ -23,11 +26,11 @@ proc map[T](s: seq[T], f: ApplicateArg): seq[T] =
# `applicate do` here generates an anonymous template, so `x - 1` is inlined at AST level:
doAssert @[1, 2, 3, 4, 5].map(applicate do (x): x - 1) == @[0, 1, 2, 3, 4]
doAssert @[1, 2, 3, 4, 5].map(fromSymbol(succ)) == @[2, 3, 4, 5, 6]
# sugar for `applicate do` syntax (again, best I could come up with):
# optional operators:
doAssert @[1, 2, 3, 4, 5].map(x !=> x * 2) == @[2, 4, 6, 8, 10]
doAssert @[1, 2, 3, 4, 5].map(x \=> x * 2) == @[2, 4, 6, 8, 10]
```

See tests for more example uses of this library. Tests are ran and docs are built with [nimbleutils](https://github.com/hlaaftana/nimbleutils).

Note: Since `Applicate` is implemented as `distinct int` or `distinct string` and is also usually used as `static Applicate` (for which `ApplicateArg` is an alias), you might have a fair bit of trouble/bugs with the type system. This is unfortunate as it limits the possibilities for type annotated functional programming using applicates. The messiness of Nim's error system when dealing with macros also does not help in this regard.
Note: Since `Applicate` is implemented as `distinct ApplicateKey` and is also usually used as `static Applicate` (for which `ApplicateArg` is an alias), this library fairly pushes Nim's type system, so annotating applicates with types can be difficult. Nim macro errors in general are also not great.
4 changes: 2 additions & 2 deletions applicates.nimble
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Package

version = "0.2.1"
version = "0.3.0"
author = "hlaaftana"
description = "instantiated \"pointers\" to cached AST"
license = "MIT"
Expand All @@ -25,7 +25,7 @@ task tests, "run tests for multiple backends and defines":
when declared(runTests):
runTests(
backends = {c, #[js]#},
optionCombos = @["", #["-d:applicatesUseMacroCache", "-d:applicatesCacheUseTable"]#]
#optionCombos = @["", "-d:applicatesUseMacroCache", "-d:applicatesCacheUseTable"]
)
else:
echo "tests task not implemented, need nimbleutils"
120 changes: 9 additions & 111 deletions src/applicates.nim
Original file line number Diff line number Diff line change
@@ -1,49 +1,7 @@
import macros

const cacheUseTable = defined(applicatesCacheUseTable) and not defined(nimdoc)
const useCache = defined(applicatesUseMacroCache) and not defined(nimdoc)

when cacheUseTable:
type ApplicateKey* = string
else:
type ApplicateKey* = int
## "pointer to" (index of) applicate AST. if you define
## ``applicatesCacheUseTable`` this will be a string

type
Applicate* = distinct ApplicateKey
## distinct version of ApplicateKey
ApplicateArg* = static Applicate
## `static Applicate` to use for types of arguments

when useCache:
import macrocache
const applicateCache* =
when cacheUseTable:
CacheTable "applicates.applicates.table"
else:
CacheSeq "applicates.applicates"
elif cacheUseTable:
import tables
var applicateCache* {.compileTime.}: Table[string, NimNode]
else:
var applicateCache* {.compileTime.}: seq[NimNode]
## the cache containing the routine definition nodes of
## each applicate. can be indexed by the ID of an applicate to receive
## its routine node, which is meant to be put in user code and invoked
##
## uses a compileTime seq by default, if you define `applicatesUseMacroCache`
## then it will use Nim's `macrocache` types, if you define
## `applicatesCacheUseTable` then it will use a `CacheTable` with
## unique strings

proc registerApplicate*(node: NimNode): ApplicateKey =
let num = len(applicateCache)
result = when cacheUseTable: $num else: num
when cacheUseTable:
applicateCache[result] = node
else:
add(applicateCache, node)
import applicates/internals
export ApplicateKey, Applicate, ApplicateArg

macro makeApplicate*(body): untyped =
## Registers given routine definitions as applicates and
Expand All @@ -61,9 +19,7 @@ macro makeApplicate*(body): untyped =
result = newNimNode(nnkStmtList, body)
for st in body: result.add(getAst(makeApplicate(st)))
of RoutineNodes:
let num = len(applicateCache)
#let key = when cacheUseTable: $num else: num
# ^ is this even going to work in the future
let num = applicateCount()
let b = newNimNode(
if body.kind in {nnkDo, nnkLambda}:
nnkProcDef
Expand All @@ -81,18 +37,14 @@ macro makeApplicate*(body): untyped =
of nnkMethodDef: nskMethod
of nnkFuncDef: nskFunc
else: nskTemplate, "appl" & $num)
let key = registerApplicate(b)
let key = registerApplicate(b, num)
result = newCall(bindSym"Applicate", newLit(key))
if body[0].kind != nnkEmpty:
result = newConstStmt(
if body[0].kind in {nnkSym, nnkClosedSymChoice, nnkOpenSymChoice}:
ident repr body[0]
else:
body[0], result)
#[when cacheUseTable:
applicateCache[key] = b
else:
add(applicateCache, b)]#
else:
error("cannot turn non-routine into applicate, given kind " & $body.kind, body)

Expand All @@ -118,7 +70,9 @@ macro makeApplicateFromTyped*(body: typed): untyped =
error("cannot turn non-routine into applicate, given kind " & $body.kind, body)

macro makeTypedApplicate*(body: untyped): untyped =
## Injects `used` pragma into `body` and calls `makeApplicateFromTyped`.
## Calls `makeApplicateFromTyped` without giving an unused warning.
##
## Accomplishes this by injecting {.used.}.
runnableExamples:
makeTypedApplicate:
template useIt =
Expand Down Expand Up @@ -317,35 +271,6 @@ macro toUntyped*(sym: untyped, arity: static int): Applicate =
call.add(temp)
result = getAst(applicate(params, call))

proc inferArity*(sym: NimNode): int =
## infers arity of symbol
##
## -1 if sym is not a symbol, -2 if implementation
## of a symbol was nil, -3 if symbol choice arities
## do not match
case sym.kind
of nnkSym:
let impl = sym.getImpl
if impl.isNil: return -2 # symbol impl nil
let fparams = impl[3]
for i in 1..<fparams.len:
result += fparams[i].len - 2
of nnkClosedSymChoice, nnkOpenSymChoice:
for i in 0..<sym.len:
let s = sym[i]
let impl = s.getImpl
if impl.isNil: return -2 # symbol impl nil
let fparams = impl[3]
var arity = 0
for i in 1..<fparams.len:
arity += fparams[i].len - 2
if i == 0:
result = arity
elif result != arity:
result = -3 # symbol arities not shared
else:
result = -1

macro toUntyped*(sym: typed): Applicate =
## infers the arity of `sym` from its symbol then calls `toUntyped(sym, arity)`
##
Expand All @@ -372,37 +297,13 @@ macro toUntyped*(sym: typed): Applicate =
let identSym = ident repr sym
result = getAst(toUntyped(identSym, arity))

proc node*(appl: Applicate): NimNode {.compileTime.} =
## retrieves the node of the applicate from the cache
result = applicateCache[(when cacheUseTable: string else: int)(appl)]
# macrocache should copy automatically, but it doesn't yet:
result = copy result

macro arity*(appl: ApplicateArg): static int =
## gets arity of applicate. check `inferArity` for meaning of
## negative values
runnableExamples:
doAssert arity((x, y) !=> x + y) == 2
doAssert arity(a !=> a) == 1
doAssert arity(!=> 3) == 0
let n = appl.node
case n.kind
of RoutineNodes:
let fparams = n[3]
var res = 0
for i in 1..<fparams.len:
res += fparams[i].len - 2
result = newLit(res)
else:
result = newLit(inferArity(n))

macro instantiateAs*(appl: ApplicateArg, name: untyped): untyped =
## instantiates the applicate in the scope with the given name
##
## helps where `apply` syntax isn't enough (for example generics
## and overloading)
runnableExamples:
instantiateAs(x !=> x + 1, incr)
instantiateAs(fromSymbol(system.succ), incr)
doAssert incr(1) == 2
proc foo[T](x, y: T): T {.makeApplicate.} = x + y
proc bar(x, y: string): string {.makeApplicate.} = x & y
Expand Down Expand Up @@ -439,7 +340,7 @@ macro apply*(appl: ApplicateArg, args: varargs[untyped]): untyped =
## applies the applicate by injecting the applicate routine
## (if not in scope already) then calling it with the given arguments
runnableExamples:
const incr = x !=> x + 1
const incr = fromSymbol(system.succ)
doAssert incr.apply(1) == 2
let a = appl.node
case a.kind
Expand Down Expand Up @@ -481,6 +382,3 @@ macro forceApply*(appl: ApplicateArg, args: varargs[untyped]): untyped =
else:
result = newCall(a)
for arg in args: result.add(arg)

import applicates/operators
export operators
104 changes: 104 additions & 0 deletions src/applicates/internals.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import macros

const cacheUseTable = defined(applicatesCacheUseTable) and not defined(nimdoc)
const useCache = defined(applicatesUseMacroCache) and not defined(nimdoc)

when cacheUseTable:
type ApplicateKey* = string
else:
type ApplicateKey* = int
## "pointer to" (index of) applicate AST. if you define
## ``applicatesCacheUseTable`` this will be a string

type
Applicate* = distinct ApplicateKey
## distinct version of ApplicateKey
ApplicateArg* = static Applicate
## `static Applicate` to use for types of arguments

when useCache:
import macrocache
const applicateCache* =
when cacheUseTable:
CacheTable "applicates.applicates.table"
else:
CacheSeq "applicates.applicates"
elif cacheUseTable:
import tables
var applicateCache* {.compileTime.}: Table[string, NimNode]
else:
var applicateCache* {.compileTime.}: seq[NimNode]
## the cache containing the routine definition nodes of
## each applicate. can be indexed by the ID of an applicate to receive
## its routine node, which is meant to be put in user code and invoked
##
## uses a compileTime seq by default, if you define `applicatesUseMacroCache`
## then it will use Nim's `macrocache` types, if you define
## `applicatesCacheUseTable` then it will use a `CacheTable` with
## unique strings

template applicateCount*(): int = # this breaks when `proc {.compileTime.}`
## total number of registered applicates
len(applicateCache)

proc registerApplicate*(node: NimNode, num: int = applicateCount()): ApplicateKey {.compileTime.} =
result = when cacheUseTable: $num else: num
# ^ is this even going to work in the future
when cacheUseTable:
applicateCache[result] = copy node
else:
add(applicateCache, copy node)

proc inferArity*(sym: NimNode): int =
## infers arity of symbol
##
## -1 if sym is not a symbol, -2 if implementation
## of a symbol was nil, -3 if symbol choice arities
## do not match
case sym.kind
of nnkSym:
let impl = sym.getImpl
if impl.isNil: return -2 # symbol impl nil
let fparams = impl[3]
for i in 1..<fparams.len:
result += fparams[i].len - 2
of nnkClosedSymChoice, nnkOpenSymChoice:
for i in 0..<sym.len:
let s = sym[i]
let impl = s.getImpl
if impl.isNil: return -2 # symbol impl nil
let fparams = impl[3]
var arity = 0
for i in 1..<fparams.len:
arity += fparams[i].len - 2
if i == 0:
result = arity
elif result != arity:
result = -3 # symbol arities not shared
else:
result = -1

proc node*(appl: Applicate): NimNode {.compileTime.} =
## retrieves the node of the applicate from the cache
result = applicateCache[(when cacheUseTable: string else: int)(appl)]
# macrocache should copy automatically, but it doesn't yet:
result = copy result

proc arity*(appl: Applicate): int {.compileTime.} =
## gets arity of applicate. check `inferArity` for meaning of
## negative values
runnableExamples:
import ../applicates, ./operators
doAssert static(arity((x, y) !=> x + y)) == 2
doAssert static(arity(a !=> a)) == 1
doAssert static(arity(!=> 3)) == 0
let n = appl.node
case n.kind
of RoutineNodes:
let fparams = n[3]
var res = 0
for i in 1..<fparams.len:
res += fparams[i].len - 2
result = res
else:
result = inferArity(n)
7 changes: 7 additions & 0 deletions src/applicates/operators.nim
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ../applicates, macros
template `!=>`*(params, body): untyped =
## infix version of `applicate`, same parameter syntax
runnableExamples:
import ../applicates
doAssert (x !=> x + 1).apply(2) == 3
const foo = (a, b) !=> a + b
doAssert foo.apply(1, 2) == 3
Expand All @@ -15,6 +16,7 @@ template `!=>`*(body): untyped =
template `\=>`*(params, body): untyped =
## alias for `!=>`
runnableExamples:
import ../applicates
doAssert (x \=> x + 1).apply(2) == 3
const foo = (a, b) \=> a + b
doAssert foo.apply(1, 2) == 3
Expand Down Expand Up @@ -44,6 +46,7 @@ macro `|`*(appl: ApplicateArg, arg: untyped): untyped =
##
## note that you can undefine operators you don't want via ``import except``
runnableExamples:
import ../applicates
doAssert (x !=> x + 1) | 1 == 2
const foo = x !=> x + 1
doAssert foo | 1 == 2
Expand Down Expand Up @@ -106,6 +109,8 @@ macro `|>`*(value, call): untyped =
##
## if call is a dot call, it is not modified, so value becomes the second argument in the call
runnableExamples:
import ../applicates

const incr = fromSymbol(system.succ)
const multiply = fromSymbol(`*`)
const divide = fromSymbol(`/`)
Expand All @@ -120,6 +125,8 @@ macro `|>`*(value, call): untyped =
macro chain*(initial, calls): untyped =
## statement list chained version of `|>`
runnableExamples:
import ../applicates

const incr = fromSymbol(system.succ)
const multiply = fromSymbol(`*`)
const divide = fromSymbol(`/`)
Expand Down
4 changes: 3 additions & 1 deletion tests/test_basic.nim
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ test "applicate macro and apply":

named.apply(upperA, char, 65)
doAssert upperA == 'A'


import applicates/operators

test "operators":
check (x !=> x[^1]) | "abc" == 'c'
check \((name, value) \=> (let name = value; name))(a, 3) == 3
Expand Down
Loading

0 comments on commit 2951ef4

Please sign in to comment.