summary refs log tree commit diff stats
path: root/compiler/modules.nim
blob: 8fedba10ad55c7b46ad050cc49c176a1960b4bc0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#
#
#           The Nim Compiler
#        (c) Copyright 2015 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## Implements the module handling, including the caching of modules.

import
  ast, astalgo, magicsys, std / sha1, msgs, cgendata, sigmatch, options,
  idents, os, lexer, idgen, passes, syntaxes, llstream, modulegraphs, rod,
  lineinfos, pathutils

proc resetSystemArtifacts*(g: ModuleGraph) =
  magicsys.resetSysTypes(g)

proc newModule(graph: ModuleGraph; fileIdx: FileIndex): PSym =
  # We cannot call ``newSym`` here, because we have to circumvent the ID
  # mechanism, which we do in order to assign each module a persistent ID.
  new(result)
  result.id = -1             # for better error checking
  result.kind = skModule
  let filename = toFullPath(graph.config, fileIdx)
  result.name = getIdent(graph.cache, splitFile(filename).name)
  if not isNimIdentifier(result.name.s):
    rawMessage(graph.config, errGenerated, "invalid module name: " & result.name.s)

  result.info = newLineInfo(fileIdx, 1, 1)
  let
    pck = getPackageName(graph.config, filename)
    pck2 = if pck.len > 0: pck else: "unknown"
    pack = getIdent(graph.cache, pck2)
  var packSym = graph.packageSyms.strTableGet(pack)
  if packSym == nil:
    packSym = newSym(skPackage, getIdent(graph.cache, pck2), nil, result.info)
    initStrTable(packSym.tab)
    graph.packageSyms.strTableAdd(packSym)

  result.owner = packSym
  result.position = int fileIdx

  if int(fileIdx) >= graph.modules.len:
    setLen(graph.modules, int(fileIdx) + 1)
  #growCache graph.modules, int fileIdx
  graph.modules[result.position] = result

  incl(result.flags, sfUsed)
  initStrTable(result.tab)
  strTableAdd(result.tab, result) # a module knows itself
  let existing = strTableGet(packSym.tab, result.name)
  if existing != nil and existing.info.fileIndex != result.info.fileIndex:
    localError(graph.config, result.info,
      "module names need to be unique per Nimble package; module clashes with " &
        toFullPath(graph.config, existing.info.fileIndex))
  # strTableIncl() for error corrections:
  discard strTableIncl(packSym.tab, result)

proc compileModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymFlags): PSym =
  result = graph.getModule(fileIdx)
  if result == nil:
    result = newModule(graph, fileIdx)
    result.flags = result.flags + flags
    if sfMainModule in result.flags:
      graph.config.mainPackageId = result.owner.id

    result.id = getModuleId(graph, fileIdx, toFullPath(graph.config, fileIdx))
    discard processModule(graph, result,
      if sfMainModule in flags and graph.config.projectIsStdin: stdin.llStreamOpen else: nil)
  elif graph.isDirty(result):
    result.flags.excl sfDirty
    # reset module fields:
    initStrTable(result.tab)
    result.ast = nil
    discard processModule(graph, result,
      if sfMainModule in flags and graph.config.projectIsStdin: stdin.llStreamOpen else: nil)
    graph.markClientsDirty(fileIdx)

proc importModule*(graph: ModuleGraph; s: PSym, fileIdx: FileIndex): PSym {.procvar.} =
  # this is called by the semantic checking phase
  assert graph.config != nil
  result = compileModule(graph, fileIdx, {})
  graph.addDep(s, fileIdx)
  #if sfSystemModule in result.flags:
  #  localError(result.info, errAttemptToRedefine, result.name.s)
  # restore the notes for outer module:
  graph.config.notes =
    if s.owner.id == graph.config.mainPackageId: graph.config.mainPackageNotes
    else: graph.config.foreignPackageNotes

proc includeModule*(graph: ModuleGraph; s: PSym, fileIdx: FileIndex): PNode {.procvar.} =
  result = syntaxes.parseFile(fileIdx, graph.cache, graph.config)
  graph.addDep(s, fileIdx)
  graph.addIncludeDep(s.position.FileIndex, fileIdx)

proc connectCallbacks*(graph: ModuleGraph) =
  graph.includeFileCallback = includeModule
  graph.importModuleCallback = importModule

proc compileSystemModule*(graph: ModuleGraph) =
  if graph.systemModule == nil:
    connectCallbacks(graph)
    graph.config.m.systemFileIdx = fileInfoIdx(graph.config,
        graph.config.libpath / RelativeFile"system.nim")
    discard graph.compileModule(graph.config.m.systemFileIdx, {sfSystemModule})

proc wantMainModule*(conf: ConfigRef) =
  if conf.projectFull.isEmpty:
    fatal(conf, newLineInfo(conf, AbsoluteFile"command line", 1, 1), errGenerated,
        "command expects a filename")
  conf.projectMainIdx = fileInfoIdx(conf, addFileExt(conf.projectFull, NimExt))

proc compileProject*(graph: ModuleGraph; projectFileIdx = InvalidFileIDX) =
  connectCallbacks(graph)
  let conf = graph.config
  wantMainModule(conf)
  let systemFileIdx = fileInfoIdx(conf, conf.libpath / RelativeFile"system.nim")
  let projectFile = if projectFileIdx == InvalidFileIDX: conf.projectMainIdx else: projectFileIdx
  graph.importStack.add projectFile
  if projectFile == systemFileIdx:
    discard graph.compileModule(projectFile, {sfMainModule, sfSystemModule})
  else:
    graph.compileSystemModule()
    discard graph.compileModule(projectFile, {sfMainModule})

proc makeModule*(graph: ModuleGraph; filename: AbsoluteFile): PSym =
  result = graph.newModule(fileInfoIdx(graph.config, filename))
  result.id = getID()

proc makeModule*(graph: ModuleGraph; filename: string): PSym =
  result = makeModule(graph, AbsoluteFile filename)

proc makeStdinModule*(graph: ModuleGraph): PSym = graph.makeModule(AbsoluteFile"stdin")
urns a JSValue, the native data type of QuickJS. fromJS returns a Result[T, JSError], which is interpreted as follows: * ok(T) is successful conversion. * err(JSError) is an error in the conversion. * ok(nil) for reference types is null. For non-nullable types, null is ok(none(T)). * err(nil) is JS_EXCEPTION, i.e. an exception has been thrown and is being propagated. An additional point of interest is reference types: ref types registered with the registerType macro can be freely passed to JS, and the function- defining macros set functions on their JS prototypes. When a ref type is passed to JS, a shim JS object is associated with the Nim object, and will remain in memory until neither Nim nor JS has references to it. Effectively, this means that you can expose Nim objects to JS and take Nim objects as arguments through the automagical .jsfunc pragma (& friends) without having to bother with error-prone manual reference counting. How this is achieved is detailed below. (You generally don't need the following info unless you're debugging the JS type conversion logic, in which case I offer my condolences.) In fact, there is a complication in this system: QuickJS has a reference- counting GC, but Nim also has a reference-counting GC. Associating two objects that are managed by two separate GCs is problematic, because even if you can freely manage the references on both objects, you now have a cycle that only a cycle collector can break up. A cross-GC cycle collector is obviously out of question; then it would be easier to just replace the entire GC in one of the runtimes. So instead, we hook into the QuickJS cycle collector (through a custom patch). Every time a JS companion object of a Nim object would be freed, we first check if the Nim object still has references from Nim, and if yes, prevent the JS object from being freed by "moving" a reference to the JS object (i.e. unref Nim, ref JS). Then, if we want to pass the object to JS again, we add no references to the JS object, only to the Nim object. By this, we "moved" the reference back to JS. This way, the Nim cycle collector can destroy the object without problems if no more references to it exist. But also, if you set some properties on the JS companion object, it will remain even if no more references exist to it in JS for some time, only in Nim. i.e. this works: ```js document.querySelector("html").canary = "chirp"; console.log(document.querySelector("html").canary); /* chirp */ ``` ### JS in the pager Keybindings can be assigned JavaScript functions in the config, and then the pager executes those when the keybindings are pressed. Also, contents of the start.startup-script option are executed at startup. This is used when `cha` is called with the `-r` flag. There *is* an API, described at [api.md](api.md). Web APIs are exposed to pager too, but you cannot operate on the DOMs themselves from the pager, unless you create one yourself with DOMParser.parseFromString. [config.md](config.md) describes all commands that are used in the default config. ### JS in the buffer The DOM is implemented through the same wrappers as those in pager. (Obviously, the pager modules are not exposed to buffer JS.) Aside from document.write, it is mostly straightforward, and usually works OK, though too many things are missing to really make it useful. As for document.write: don't ask. It works as far as I can tell, but I wouldn't know why. ## Styling css/ contains everything related to styling: CSS parsing and cascading. The parser is not very interesting, it's just an implementation of the CSS 3 parsing module. The latest iteration of the selector parser is pretty good. The media query parser is horrible and should be rewritten. And the CSS value parser works OK, but is missing features like variables. Cascading is slow, though it could be slower. Chawan has style caching, so re-styles are normally very fast. Also, a hash map is used for reducing initial style calculation times. However, we don't have a Bloom filter yet. ## Layout Layout can be found in the layout/ module. It has some problems: * CSS was designed for pixel-based displays, not for character-based ones. So we have to round a lot, and sometimes this goes wrong. (This is mostly solved by some basic heuristics inside the layout engine.) * Some (now) commonly used features like grid are not implemented, so websites using those look ugly. * It's slow on large documents, because we don't have partial layouting capabilities. Our layout engine is a rather simple procedural layout implementation. It runs in two passes. In the first pass, it generates the layout tree; this is important because rules for generating anonymous boxes are surprisingly involved. (Specifically, anonymous inline box handling is kind of a mess.) The second pass then does the actual arrangement of the boxes on the screen. The output tree uses relative coordinates; that is, every box is positioned relative to its parent. Layout is fully recursive. This means that after a certain nesting depth, the buffer will run out of stack space and promptly crash. Since we do not cache layout results, and the whole page is layouted (no partial layouting), it gets quite slow on large documents. ### Rendering After layout is finished, the document is rendered onto a text-based canvas, which is represented as a sequence of strings associated with their formatting. Again, the entire document is rendered, which is the main reason why Chawan performs poorly on large documents. The positive side of this is that search is very simple (and fast), since we are just running regexes over a linear sequence of strings.