Right now the standard library and how it connects to the actual OS primitives a mix of different approaches.
Let's look at a few examples. lib/pure/os.nim, sleep
proc sleep*(milsecs: int) {.rtl, extern: "nos$1", tags: [TimeEffect], noWeirdTarget.} =
## Sleeps `milsecs` milliseconds.
when defined(windows):
winlean.sleep(int32(milsecs))
else:
var a, b: Timespec
a.tv_sec = posix.Time(milsecs div 1000)
a.tv_nsec = (milsecs mod 1000) * 1000 * 1000
discard posix.nanosleep(a, b)
This is nice example, usually there are more when,elif etc. Here we see that inside the function there are 'when' switches that then brings in the OS dependent stuff. Many functions in the library are like this. Thankfully they are often partitioned in one single chunk for each. There are standard libraries for other languages I've seen where they have several chunks for the same OS, one of types, then some code, then some types again, basically litter the the equivalent of Nim's 'when'. This approach is the one I like the least.
Let's look at lib/core/locks.nim
proc initLock*(lock: var Lock) {.inline.} =
## Initializes the given lock.
when not defined(js):
initSysLock(lock)
proc deinitLock*(lock: var Lock) {.inline.} =
## Frees the resources associated with the lock.
deinitSys(lock)
proc tryAcquire*(lock: var Lock): bool {.inline.} =
## Tries to acquire the given lock. Returns `true` on success.
result = tryAcquireSys(lock)
proc acquire*(lock: var Lock) {.inline.} =
## Acquires the given lock.
when not defined(js):
acquireSys(lock)
proc release*(lock: var Lock) {.inline.} =
## Releases the given lock.
when not defined(js):
releaseSys(lock)
Here we can see that the actual implementation is delegated to *Sys function, like tryAcquire calls tryAcquireSys. The OS dependent implementation is then found in lib/std/private/syslocks.nim. Here we can see actual implementation for each platform which are selected with a 'when' clause. This approach is nicer because now we have separated the generic part and the OS dependent part.
Now there is step further you can take with this example and that is why not let an OS dependent file implement the functions directly, like tryAcquire. So the implementation is just included per module basis depending on you OS. Basically, the module would start with
when defined(windows)
import windows/locks.nim
elif defined(posix)
import posix/locks.nim # This file selects further when it comes POSIX variants
elif defined(...)
...
else:
..
Right now the standard library is a mix of everything and I've not seen any guidelines which approach the project should take. If there is no document or guideline describing this then we will have this mixture of everything. What approach should be taken?
You're right that we have no guideline but the following is the guideline that we should have:
# Forward declarations that describe the interface
proc featureA*() {.tags: <explicit list>, raises: <explicit list>, gcsafe etc.}
## Documentation
proc featureB*() {.tags: <explicit list>, raises: <explicit list>, gcsafe etc.}
## Documentation
when defined(posix):
include impl_posix
elif defined(windows):
include impl_windows
else:
include impl_fallback
Advantages:
Disadvantages:
That pattern is good for larger differences. Though I personally don't mind the when/else patterns in most of the procs for handling smaller os diffs.
The main downside with using include is that it breaks IDEs / nimsuggest which makes contributing harder for new or occasional contributors. It's really annoying to open a file and get errors the entire way down. You also can't use the "goto symbol definition" either.
Just my experience using the tooling as it is. I'll have to look into setting that up for the compiler project. Usually it works well enough, aside from the includes.
If I open up a project with a Nimble root can't the editor use that to find the main nim file? Seems odd it wouldn't use that info. If not it seems like it could use a PR. :)
If I open up a project with a Nimble root can't the editor use that to find the main nim file? Seems odd it wouldn't use that info.
That's how nimsuggest finds the main .nim file. nimsuggest can do it for the poor editor if only the editor would let it...
I was able to get highlighting by manually setting the project file.
Nice, yah I looked at the VSCode Nim based extension. Its confusing with all the vscode/nodejs file apis, but fundamentally it just calls some form of execProc to start nimsuggest. Impressive that @saem got it running with Nim bindings.
If nimsuggest just finds the default nimble file, then it'd be possible to modify the vscode extension to try that, and failing that to start a per file instance. Though nimsuggest would probably need to be able to print if it found a project nimble or something? I'll toy with it.
I was hoping the nim-lsp would do that, but it wants some json config. I prefer it to do the "sane thing" first then let me customize it if needed.