# Hacking
Some notes on modifying Chawan's code.
## Style
Refer to the NEP1 for the basics. Also, try to keep the style of existing code.
In particular, style of vendored libraries should not be changed unless upstream is dead.
### Casing
Everything is camelCase. Enums are camelCase too, but the first part is an
abbreviation of the type name. e.g. members of SomeEnum start with se.
Exceptions:
- Types/constants use PascalCase. enums in cssvalues use PascalCase too, to avoid name collisions.
- We keep style of external C libraries, which is often snake_case.
Rationale: consistency.
### Wrapping
80 chars per line.
Exceptions: URL comments.
Rationale: makes it easier to edit in vi.
### Spacing
No blank lines inside procedures (and other code blocks). A single blank line separates two procs, type defs, etc. If your proc doesn't fit on two 24-line screens, split it up into more procs instead of inserting blank lines.
Exceptions: occasionally a proc will get larger than two screens, and that's ok, but try to avoid it.
Rationale: makes it easier to edit in vi.
### Param separation
Semicolons, not commas. e.g.
proc foo(p1: int; p2, p3: string; p4 = true) # Good proc bar(p1: int, p2, p3: string, p4 = true) # Bad
Rationale: makes it easier to edit in vi.
### Naming
Prefer short (but descriptive) names. Don't copy verbose naming from the standard.
Rationale: we aren't a fruit company.
### Comments
Comment what is not obvious, don't comment what is obvious. Whether something is obvious or not is left to your judgment.
Don't paste standard prose into the code unless you're making a point. If you do, abridge the prose.
Rationale: common sense, copyright.
### C style
Note that most C code included in this repository is from vendored external libraries like QuickJS; following points only apply to code we own, such as chaseccomp.
- 80 characters per line. Indent with 4 spaces (not mixed with tabs).
- Local variable declarations come before their use (a la C89). The two sections have a blank line inbetween.
- Braces go on the same line as the statement, except for functions where they go on the next line.
if,for, etc. must have braces unless each part fits on a single line.- Boolean operators (
||etc.) belong on the same line as the left-hand expression.
### JavaScript style
Similar to C style.
- 80 characters per line. Indent with 4 spaces (not mixed with tabs).
- Prefer
const/lettovar. - Braces go on the same line as the statement or function.
if,for, etc. must have braces unless each part fits on a single line.- Boolean operators (
||etc.) belong on the same line as the left-hand expression.
Note: although QJS has an optimizer, compared to that of a static language it is very limited. Some ugliness is acceptable if it results in more efficient code.
### Fixing cyclic imports
In Nim, you can't have circular dependencies between modules. This gets unwieldy as the HTML/DOM/etc. specs are a huge cyclic OOP mess.
The preferred workaround is using the foreign function interface:
# Forward declaration hack
proc forwardDecl(window: Window; x, y: int) {.importc: "cha_$1".}
# in the other module:
proc forwardDecl(window: Window; x, y: int) {.exportc: "cha_$1".} =
# [...]
Don't forget to comment "Forward declaration hack" above. (Hopefully we can remove these once Nim supports cyclic module dependencies.)
## Features to avoid
List of Nim features/patterns that sound like a good idea but aren't, for non-obvious reasons.
### Exceptions
Avoid at all costs. If a standard library procedure throws, do not use it.
Also avoid try...finally (even without except).
For own procs, use Result/Opt instead. For large objects this may result in
excessive copying; to avoid inefficient code, either make them ref or use
manual RVO:
proc blah(input: int; output: var BigObject): Opt[void] =
if input < 0:
return err()
output = BigObject(...)
ok()
In new modules, always specify:
# This is an example module.
# After any header comments, but before imports:
{.push raises: [].}
import ...
# At the end of the file:
{.pop.} # raises: []
### Implicit initialization
Usually, the correct way to create an object is:
let myObj = MyObject( param1: x, param2: y # if there's e.g. a param3 too, it's OK to leave it out and let it be # default initialized. )
For primitive types, just set them to 0, "", etc.
As a special case, implicit initialization is recommended for arrays:
var buf1: array[1234, char] # when you need 0-initialization
var buf2 {.noinit.}: array[1234, char] # when you don't need 0-initialization
Note that the second example is unsafe and doesn't work with GC'ed types, so don't use it unless you know what you're doing.
### out parameters
out parameters crash the 1.6.14 compiler. Use var instead.
### .cursor
.cursor is broken for custom types on Nim versions up to 2.0.0.
For storing weak references, use ptr instead.
### .union
This is C's untagged union, as such it is inherently unsafe (for example,
its fields may not refer to GC'ed memory). In most cases you'll want to
use a case object instead.
On the other hand, .union is required for some optimizations. In such
cases, always add unionHook before any other procedures:
type MyUnion {.union.} = object
# blah...
unionHook(MyUnion)
This is a workaround for a compiler bug affecting Nim versions up to & including 2.2.8.
### Copying operations
substr and x[n..m] copies. Try to use toOpenArray instead, which is a
non-copying slice. (Obviously, you should use substr if you need to
copy.)
Note that = usually copies. If you're copying a large object a lot, you
may want to set its type to ref. Also, the move statement is useful
when copying values out of an object; note that this zeroes out the
original value, so it's safe to use.
Beware of pairs on sequences of objects; it copies. Use mypairs if you
don't need mutation, mpairs if you do:
proc foo(objs: openArray[SomeObj]) =
for i, obj in objs: # Bad. this copies. obj's type is "SomeObj".
obj.bar(i)
# import utils/twtstr to use mypairs.
for i, obj in objs.mypairs: # OK, doesn't copy. obj's type is "lent "SomeObj".
obj.bar(i)
for i, obj in objs.mpairs: # OK, doesn't copy. obj's type is "var SomeObj".
obj.i = i
Similarly, the regular items on arrays copies; use myitems instead.
### func, .noSideEffect
These introduce function coloring for little to no benefit.
Just use proc without .noSideEffect.
### Closures
A closure is an anonymous proc that uses ("captures") variables in its
wrapper proc. These introduce implicit GC'ed environments which are
often wasteful, and combined with JS they are an easy way to produce memory
leaks.
To enforce this rule, proc variables or members always have a nimcall
(or cdecl) pragma. The environment can be passed explicitly, e.g.:
type
FooCallback = proc(foo: Foo; opaque: RootRef) {.nimcall, raises: [].}
Foo = ref object
callback: FooCallback
opaque: RootRef # add an explicit environment like this if needed
### Globals
In most cases, context-style objects should be preferred. Nevertheless, globals are acceptable if the following circumstances are fulfilled:
- Creating multiple instances of the variable makes no sense in a single process.
- Passing the variable everywhere would be extremely tedious, wasteful, or straight up impossible (e.g. signal handlers).
Examples are interning maps (which exist to dedupe immutable objects) and QuickJS class ids (which by design are shared across runtimes).
Globals should always be tagged with the .global pragma, even in the
global scope:
var myVariable {.global.}: int
### Standard library
For most std modules, Chawan has its own counterparts in the utils directory:
std/strutils,std/unicode->utils/twtstrstd/os->utils/chaosstd/hashes->utils/chahashstd/tables->utils/tabutil
There are several reasons for not using std: poor performance from using old idioms, unpredictable backwards-compatibility, code bloat, and bugs.
This doesn't mean using std is /forbidden/, there are still many modules
for which we don't have (or need) custom counterparts - for instance,
std/algorithm works well. The only rule is that when you do use std,
you should prefix the imports with std/, e.g.:
import algorithm # bad import std/algorithm # good
### converter
Avoid, it slows down the compiler to a crawl.
## Debugging
Note: following text assumes you are compiling in debug mode, i.e.
make TARGET=debug.
### The universal debugger
"eprint x, y" prints x, y to stderr, space separated.
Normally you can view what you printed through the M-c M-c (escape + c
twice) console. Except when you're printing from the pager, then do
cha [...] 2>a and check the "a" file.
Sometimes, printing to the console triggers a self-feeding loop of printing
to the console. To avoid this, disable the console buffer:
cha [...] -o start.console-buffer=false 2>a. Then check the "a" file.
You can also inspect open buffers from the console. Note that you must run these before switching to the console buffer (i.e. before the second M-c), or it will show info about the console buffer.
pager.process: the current buffer's PID.pager.cacheFile: the current buffer's cache file.pager.cacheId: the cache ID of said file. Open thecache:idURL to view the file.
### gdb
gdb should work fine too. You can attach it to buffers by putting a long
sleep call in runBuffer, then retrieving the PID as described above. Note
that this will upset seccomp, so you should compile with
make TARGET=debug DANGER_DISABLE_SANDBOX=1.
### Debugging layout bugs
One possible workflow:
- Save page from your favorite graphical browser.
- Binary search the HTML by deleting half of the file at each step. Be careful to not remove any stylesheet LINK or STYLE tags.
- Binary search the CSS using the same method. You can format it using the graphical browser's developer tools.
The -o start.console-buffer=false trick (see above) is especially useful
when debugging a flow layout path that the console buffer also needs.
Don't forget to add a test case after the fix:
$ ./test/layout/add test/layout/my-test-case.html
### Debugging JavaScript issues
While Chawan lacks a real debugger, it is possible to "mock" resources as follows for printf (or, well, console.log) debugging.
First, check the URL of the JS file to replace, and add to ~/.chawan/browsecap:
https; /cgi-bin/get-custom-js %s; cgioutput; resource; match=https://example\.org/js/blah\.js
Then, save blah.js in ~/.chawan/mock/blah.js (e.g., using s S), and
create a CGI script in ~/.chawan/cgi-bin/get-custom-js:
#!/bin/sh echo Content-Type: text/javascript echo case $1 in *blah.js) cat ../mock/blah.js esac
Now you can edit ~/.chawan/mock/blah.js to your heart's content until the source of the issue becomes clear.
### Sandbox violations
You got a syscall number (assuming you're on Linux); look it up in the Linux syscall table for your architecture.
To get more context on what happened, you can run
strace -f ./cha -o start.console-buffer=false [...] 2>a, trigger the
crash, then search for the last occurrence of "--- SIGSYS". Then search
backwards on the PID to see the last syscalls.
(strace also shows the syscall name, so it may be easier to check like that than looking it up in the syscall table.)
## Resources
You may find these links useful.
### Chawan
Of particular interest in the documentation are:
- The architecture document.
- The JS guide, for working with JS-related code.
### WHATWG
- HTML: https://html.spec.whatwg.org/multipage/. Includes everything and then some more.
- DOM: https://dom.spec.whatwg.org/. Includes events, basic node-related stuff, etc.
- Encoding: https://encoding.spec.whatwg.org/. These algorithms are implemented in encoding.
- URL: https://url.spec.whatwg.org/. For some incomprehensible reason, it's defined as an equally incomprehensible state machine. types/url implements this.
- Fetch: https://fetch.spec.whatwg.org/. Networking stuff. Also see https://xhr.spec.whatwg.org for XMLHttpRequest.
- Web IDL: https://webidl.spec.whatwg.org/. Relevant for JS bindings.
Note that some of these are updated daily, such as the HTML standard.
### CSS standards
- CSS 2.1: https://www.w3.org/TR/CSS2/. There's also an "Editor's Draft" 2.2 version: https://drafts.csswg.org/css2/, but so far I haven't encountered significant differences.
Good news is that unlike WHATWG specs, this doesn't change daily. Bad news is that CSS 2.1 was the last real CSS version, and newer features are spread across a bunch of random documents with questionable status of stability: https://www.w3.org/Style/CSS/specs.en.html.
### Other standards
It's unlikely that you will need these, but for completeness' sake:
- TOML: https://toml.io/en/v1.1.0. config.toml's base language.
- Mailcap: https://www.rfc-editor.org/rfc/rfc1524.
- Cookies: https://www.rfc-editor.org/rfc/rfc6265.
- EcmaScript: https://tc39.es/ecma262/ is the latest draft.
### Nim docs
- Manual: https://nim-lang.org/docs/manual.html. A detailed description of all language features.
- Standard library docs: https://nim-lang.org/docs/lib.html. Everything found in the "std/" namespace.
### MDN
https://developer.mozilla.org/en-US/docs/WebMDN is useful if you don't quite understand how a certain feature is supposed to work. It also has links to relevant standards in page footers.