Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add documentation to the macrocache module #17431

Merged
merged 8 commits into from Mar 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/lib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ Core
* `locks <locks.html>`_
Locks and condition variables for Nim.

* `macrocache <macrocache.html>`_
Provides an API for macros to collect compile-time information across modules.

* `macros <macros.html>`_
Contains the AST API and documentation of Nim for writing macros.

Expand Down
191 changes: 176 additions & 15 deletions lib/core/macrocache.nim
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,199 @@
# distribution, for details about the copyright.
#

## This module provides an API for macros that need to collect compile
## time information across module boundaries in global variables.
## Starting with version 0.19 of Nim this is not directly supported anymore
## as it breaks incremental compilations.
## Instead the API here needs to be used.
## This module provides an API for macros to collect compile-time information
This conversation was marked as resolved.
Show resolved Hide resolved
## across module boundaries. It should be used instead of global `{.compileTime.}`
## variables as those break incremental compilation.
##
## The main feature of this module is that if you create `CacheTable`s or
## any other `Cache` types with the same name in different modules, their
## content will be shared, meaning that you can fill a `CacheTable` in
## one module, and iterate over its contents in another.

runnableExamples:
import std/macros

const mcTable = CacheTable"myTable"
const mcSeq = CacheSeq"mySeq"
const mcCounter = CacheCounter"myCounter"

static:
This conversation was marked as resolved.
Show resolved Hide resolved
# add new key "val" with the value `myval`
let myval = newLit("hello ic")
mcTable["val"] = myval
assert mcTable["val"].kind == nnkStrLit

# Can access the same cache from different static contexts
# All the information is retained
static:
# get value from `mcTable` and add it to `mcSeq`
mcSeq.add(mcTable["val"])
assert mcSeq.len == 1

static:
assert mcSeq[0].strVal == "hello ic"

# increase `mcCounter` by 3
mcCounter.inc(3)
assert mcCounter.value == 3


type
CacheSeq* = distinct string
## Compile-time sequence of `NimNode`s.
CacheTable* = distinct string
## Compile-time table of key-value pairs.
##
## Keys are `string`s and values are `NimNode`s.
CacheCounter* = distinct string
## Compile-time counter, uses `int` for storing the count.

proc value*(c: CacheCounter): int {.magic: "NccValue".} =
## Returns the value of a counter `c`.
runnableExamples:
static:
let counter = CacheCounter"valTest"
# default value is 0
assert counter.value == 0

inc counter
assert counter.value == 1

proc inc*(c: CacheCounter; by = 1) {.magic: "NccInc".} =
## Increments the counter `c` with the value `by`.
runnableExamples:
static:
let counter = CacheCounter"incTest"
inc counter
inc counter, 5

assert counter.value == 6

proc add*(s: CacheSeq; value: NimNode) {.magic: "NcsAdd".} =
## Adds `value` to `s`.
runnableExamples:
import std/macros
const mySeq = CacheSeq"addTest"

static:
mySeq.add(newLit(5))
mySeq.add(newLit("hello ic"))

assert mySeq.len == 2
assert mySeq[1].strVal == "hello ic"

proc incl*(s: CacheSeq; value: NimNode) {.magic: "NcsIncl".} =
## Adds `value` to `s`.
##
## .. hint:: This doesn't do anything if `value` is already in `s`.
runnableExamples:
import std/macros
const mySeq = CacheSeq"inclTest"

static:
mySeq.incl(newLit(5))
mySeq.incl(newLit(5))

proc value*(c: CacheCounter): int {.magic: "NccValue".}
proc inc*(c: CacheCounter; by = 1) {.magic: "NccInc".}
# still one element
assert mySeq.len == 1

proc add*(s: CacheSeq; value: NimNode) {.magic: "NcsAdd".}
proc incl*(s: CacheSeq; value: NimNode) {.magic: "NcsIncl".}
proc len*(s: CacheSeq): int {.magic: "NcsLen".}
proc `[]`*(s: CacheSeq; i: int): NimNode {.magic: "NcsAt".}
proc len*(s: CacheSeq): int {.magic: "NcsLen".} =
## Returns the length of `s`.
runnableExamples:
import std/macros

const mySeq = CacheSeq"lenTest"
static:
let val = newLit("helper")
mySeq.add(val)
assert mySeq.len == 1

mySeq.add(val)
assert mySeq.len == 2

proc `[]`*(s: CacheSeq; i: int): NimNode {.magic: "NcsAt".} =
## Returns the `i`th value from `s`.
runnableExamples:
import std/macros

const mySeq = CacheSeq"subTest"
static:
mySeq.add(newLit(42))
assert mySeq[0].intVal == 42

iterator items*(s: CacheSeq): NimNode =
## Iterates over each item in `s`.
runnableExamples:
import std/macros
const myseq = CacheSeq"itemsTest"

static:
myseq.add(newLit(5))
myseq.add(newLit(42))

for val in myseq:
# check that all values in `myseq` are int literals
assert val.kind == nnkIntLit

for i in 0 ..< len(s): yield s[i]

proc `[]=`*(t: CacheTable; key: string, value: NimNode) {.magic: "NctPut".}
## 'key' has to be unique!
proc `[]=`*(t: CacheTable; key: string, value: NimNode) {.magic: "NctPut".} =
## Inserts a `(key, value)` pair into `t`.
##
## .. warning:: `key` has to be unique! Assigning `value` to a `key` that is already
## in the table will result in a compiler error.
runnableExamples:
import std/macros

const mcTable = CacheTable"subTest"
static:
# assign newLit(5) to the key "value"
mcTable["value"] = newLit(5)

# check that we can get the value back
assert mcTable["value"].kind == nnkIntLit

proc len*(t: CacheTable): int {.magic: "NctLen".} =
## Returns the number of elements in `t`.
runnableExamples:
import std/macros

proc len*(t: CacheTable): int {.magic: "NctLen".}
proc `[]`*(t: CacheTable; key: string): NimNode {.magic: "NctGet".}
const dataTable = CacheTable"lenTest"
static:
dataTable["key"] = newLit(5)
assert dataTable.len == 1

proc `[]`*(t: CacheTable; key: string): NimNode {.magic: "NctGet".} =
## Retrieves the `NimNode` value at `t[key]`.
runnableExamples:
import std/macros

const mcTable = CacheTable"subTest"
static:
mcTable["toAdd"] = newStmtList()

# get the NimNode back
assert mcTable["toAdd"].kind == nnkStmtList

proc hasNext(t: CacheTable; iter: int): bool {.magic: "NctHasNext".}
proc next(t: CacheTable; iter: int): (string, NimNode, int) {.magic: "NctNext".}

iterator pairs*(t: CacheTable): (string, NimNode) =
## Iterates over all `(key, value)` pairs in `t`.
runnableExamples:
import std/macros
const mytabl = CacheTable"values"

static:
mytabl["intVal"] = newLit(5)
mytabl["otherVal"] = newLit(6)
for key, val in mytabl:
# make sure that we actually get the same keys
assert key in ["intVal", "otherVal"]

# all vals are int literals
assert val.kind == nnkIntLit

var h = 0
while hasNext(t, h):
let (a, b, h2) = next(t, h)
Expand Down