Skip to content

Commit

Permalink
initial
Browse files Browse the repository at this point in the history
  • Loading branch information
metagn committed May 4, 2023
0 parents commit 482f8d0
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 1,2 @@
github: metagn
custom: https://www.buymeacoffee.com/metagn
26 changes: 26 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 1,26 @@
name: dirtydeeds

on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]

workflow_dispatch:

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: jiro4989/setup-nim-action@v1

- name: install nimbleutils
run: nimble install -y https://github.com/metagn/nimbleutils@#HEAD

- name: install dependencies
run: nimble install -y

- name: run tests
run: nimble tests
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 1,2 @@
*.exe
*.dll
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 1,14 @@
# dirtydeeds

Quick and dirty partial application of calls with possible typed arguments.

```nim
import dirtydeeds, sequtils
assert @[1, 2, 3].map(deed _ * 7) == @[7, 14, 21]
assert @["A", "B", "C"].map(deed "foo" & (_: string)) == @["fooA", "fooB", "fooC"]
assert @['a', 'f', 'A', '0', 'c'].filter(deed contains({'a'..'z'}, _)) == @['a', 'f', 'c']
```

More uses in tests. Note that this is currently only for partial application,
things like `_ (_ - 1)` will not work.
21 changes: 21 additions & 0 deletions dirtydeeds.nimble
Original file line number Diff line number Diff line change
@@ -0,0 1,21 @@
# Package

version = "0.1.0"
author = "metagn"
description = "macro for partially applied calls"
license = "MIT"
srcDir = "src"


# Dependencies

requires "nim >= 1.0.0"

when (compiles do: import nimbleutils):
import nimbleutils

task tests, "run tests for multiple backends":
when declared(runTests):
runTests(backends = {c, js, nims})
else:
echo "tests task not implemented, need nimbleutils"
112 changes: 112 additions & 0 deletions src/dirtydeeds.nim
Original file line number Diff line number Diff line change
@@ -0,0 1,112 @@
import macros

proc extractParam(node, defaultType: NimNode, count: int): NimNode =
result = nil
case node.kind
of nnkCallKinds, nnkObjConstr:
# _(a) means param named a
if node.len == 2 and node[0].kind == nnkIdent and node[0].eqIdent"_":
result = newIdentDefs(node[1], defaultType)
result.copyLineInfo(node)
of nnkExprEqExpr, nnkAsgn:
# default value
result = extractParam(node[0], defaultType, count)
if not result.isNil:
result[2] = node[1]
of nnkExprColonExpr:
# type
result = extractParam(node[0], defaultType, count)
if not result.isNil:
result[1] = node[1]
of nnkIdent:
if node.eqIdent"_":
result = newIdentDefs(ident("_" & $count), defaultType)
result.copyLineInfo(node)
of nnkPar, nnkTupleConstr:
if node.len == 1 and node[0].kind notin {nnkPar, nnkTupleConstr}:
result = extractParam(node[0], defaultType, count)
else: discard

proc impl(node: NimNode): NimNode =
const
paramPos = 3
genericPos = 2
bodyPos = ^1
if node.kind in RoutineNodes:
result = node
else:
result = newProc(
procType = nnkLambda,
body = node)
result.copyLineInfo(node)
let defaultType =
if result.kind in {nnkTemplateDef, nnkMacroDef}:
ident"untyped"
else:
ident"auto"
if result[paramPos][0].kind == nnkEmpty:
result[paramPos][0] = defaultType
var paramCount = 0
if result[bodyPos].kind in {nnkStmtList, nnkStmtListExpr}:
var i = 0
while i < result[bodyPos].len - 1:
let e = result[bodyPos][i]
let p = extractParam(e, defaultType, paramCount)
if not p.isNil:
result[paramPos].add(p)
inc paramCount
result[bodyPos].del(i)
else:
inc i
else:
let old = result[bodyPos]
result[bodyPos] = newNimNode(nnkStmtListExpr, old)
result[bodyPos].add(old)
let
body = result[bodyPos]
callPos = body.len - 1
let call = body[callPos]
case call.kind
of nnkCallKinds, nnkObjConstr, nnkBracketExpr, nnkCurlyExpr,
nnkPar, nnkTupleConstr, nnkBracket, nnkCurly:
if call.kind in nnkCallKinds {nnkObjConstr}:
let callee = call[0]
if callee.kind == nnkBracketExpr:
# maybe generic params
var genericParams: seq[NimNode]
var i = 1
while i < callee.len:
let p = extractParam(callee[i], newEmptyNode(), paramCount)
if not p.isNil:
genericParams.add(p)
inc paramCount
callee.del(i)
else:
inc i
if genericParams.len != 0:
if result[genericPos].kind == nnkEmpty:
result[genericPos] = newNimNode(nnkGenericParams, callee)
result[genericPos].add(genericParams)
if callee.len == 1:
call[0] = callee[0]
for i in 0 ..< call.len:
let p = extractParam(call[i], defaultType, paramCount)
if not p.isNil:
result[paramPos].add(p)
inc paramCount
call[i] = p[0]
if call.kind == nnkObjConstr and
call.len > 1 and call[1].kind != nnkExprColonExpr:
body[callPos] = newNimNode(nnkCall, call)
for a in call: body[callPos].add(a)
of nnkDotExpr, nnkDerefExpr:
let p = extractParam(call[0], defaultType, paramCount)
if not p.isNil:
result[paramPos].add(p)
inc paramCount
call[0] = p[0]
else:
warning("unsupported deed node kind " & $body[callPos].kind, body[callPos])

macro deed*(node): untyped =
result = impl(node)
1 change: 1 addition & 0 deletions tests/config.nims
Original file line number Diff line number Diff line change
@@ -0,0 1 @@
switch("path", "$projectDir/../src")
50 changes: 50 additions & 0 deletions tests/test1.nim
Original file line number Diff line number Diff line change
@@ -0,0 1,50 @@
when (compiles do: import nimbleutils/bridge):
import nimbleutils/bridge
else:
import unittest
import sequtils, algorithm

import dirtydeeds

test "basic cases":
check @[1, 2, 3].map(deed _ * 7) == @[7, 14, 21]
check @["A", "B", "C"].map(deed "foo" & (_: string)) == @["fooA", "fooB", "fooC"]
check @['a', 'f', 'A', '0', 'c'].filter(deed contains({'a'..'z'}, _)) == @['a', 'f', 'c']
let a = deed (_: int) (_: int)
check a(3, 4) == 7
proc foo[T](a, b: T, x: proc (a, b: T): T): T = x(a, b)
proc foo[T](a: T, x: proc (a: T): T): T = x(a)
check foo(3, 4, deed _ _) == 7
let max0 = deed max(0, _: int)
let max0left = deed max(_: int, 0)
check max0(7) == 7
check max0(-7) == 0
check max0left(7) == 7
check max0left(-7) == 0
check foo(7, deed max(0, _)) == 7
check foo(-7, deed max(0, _)) == 0
check foo(7, deed max(_, 0)) == 7
check foo(-7, deed max(_, 0)) == 0
let b = deed (_(a): int) a
check b(12) == 24
check foo(7, deed _(a) a * 2) == 21
var s = @[5, 3, 4, 1, 9, 2]
s.sort(deed _ - _)
check s == @[1, 2, 3, 4, 5, 9]
s.sort(deed (_ a; _ b; b - a))
check s == @[9, 5, 4, 3, 2, 1]
let maxDefault0 = deed max(_: int, (_: int) = 0)
check maxDefault0(7) == 7
check maxDefault0(-7) == 0
check maxDefault0(1, 7) == 7
check maxDefault0(-1, -7) == -1

test "declaration":
proc foo {.deed.} = (_: string) & (_: char | string)
check foo("abc", 'd') == "abcd"
check foo("abc", "def") == "abcdef"
proc bar {.deed.} = max[_ T](_: T, _: T)
check bar(1, 2) == 2
check bar(-1, -2) == -1
# only works after 2.0
#template baz {.deed.} = toSeq _

0 comments on commit 482f8d0

Please sign in to comment.