diff options
author | bptato <nincsnevem662@gmail.com> | 2021-12-05 19:55:13 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2021-12-05 19:55:13 +0100 |
commit | 31117cad22df103f84cfbe97dff08debcde72a66 (patch) | |
tree | 5f9e4b0515e63e953758fe10da9fc52d45171e0f | |
parent | 0f9c94abf5ff9e4f2ce4d8d49424c9458bbf6229 (diff) | |
download | chawan-31117cad22df103f84cfbe97dff08debcde72a66.tar.gz |
Change configuration format to toml
-rw-r--r-- | doc/config.md | 30 | ||||
-rw-r--r-- | nim.cfg | 1 | ||||
-rw-r--r-- | readme.md | 10 | ||||
-rw-r--r-- | res/config | 64 | ||||
-rw-r--r-- | res/config.toml | 63 | ||||
-rw-r--r-- | res/ua.css (renamed from res/default.css) | 0 | ||||
-rw-r--r-- | src/config/config.nim | 114 | ||||
-rw-r--r-- | src/config/toml.nim | 406 | ||||
-rw-r--r-- | src/css/parser.nim | 48 | ||||
-rw-r--r-- | src/io/buffer.nim | 2 | ||||
-rw-r--r-- | src/io/pipe.nim | 0 | ||||
-rw-r--r-- | src/utils/twtstr.nim | 88 |
12 files changed, 613 insertions, 213 deletions
diff --git a/doc/config.md b/doc/config.md index 14a34608..6b3efd17 100644 --- a/doc/config.md +++ b/doc/config.md @@ -1,6 +1,7 @@ # Configuration -Currently keybindings and a user stylesheet can be configured. +Currently keybindings and a user stylesheet can be configured. The +configuration format for twt is toml. twt will look for a config file in the ~/.config/twt/ directory, so you can just copy the one from res/ there and customize that to your liking. @@ -9,26 +10,31 @@ A list of configurable options follows. ## Stylesheets -To set a user stylesheet, use the format `stylesheet "path-to-user.css"`. +To set a user stylesheet, use the format `stylesheet = "path-to-user.css"`. Relative paths are interpreted as relative to the config directory. -For now, specifying a second stylesheet will override the first one. +Specifying a second stylesheet will override the first one. ## Keybindings -"Normal" (default pager browsing) mode keybindings are configured using the -syntax - nmap <keybinding> <action> +To specify a keybinding, you will first have to specify a mode: -Similarly, "Line-edit" (line editing) mode keybindings are configured -using the syntax +* for page browsing: [page] +* for line editing: [line] - lemap <keybinding> <action> +Keybindings are configured using the syntax + + '<keybinding>' = '<action>' Where `<keybinding>` is a combination of unicode characters with or without modifiers. Modifiers are the prefixes `C-` and `M-`, which add control or escape to the keybinding respectively (essentially making `M-` the same as -`C-[`). +`C-[`). Modifiers can be escaped with the `\` sign. + +```Example: +'C-M-j' = 'CHANGE_LOCATION' # change URL when Control, Escape and j are pressed +'gg' = 'CURSOR_FIRST_LINE' # go to the first line of the page when g is pressed twice +``` `<action>` is a valid normal or line-edit mode action. A detailed description of these follows. @@ -37,7 +43,7 @@ description of these follows. <table> <tr><th>**Name**<th>**Function** -<tr><td>`NULL`<td>Do nothing +<tr><td>`NULL`<td>Do nothing (used for disabling default keybindings) <tr><td>`QUIT`<td>Exit the browser <tr><td>`CURSOR_UP`<td>Move the cursor to the previous line <tr><td>`CURSOR_DOWN`<td>Move cursor to the next line @@ -75,7 +81,7 @@ description of these follows. <tr><td>`LINE_INFO`<td>Display information about line </table> -### Line-edit mode actions +### Line-editing actions <table> <tr><th>**Name**<th>**Function** diff --git a/nim.cfg b/nim.cfg index 5fb94fee..c370ac8b 100644 --- a/nim.cfg +++ b/nim.cfg @@ -1,3 +1,4 @@ -p:"." -p:"src/" +-p:"lib/" --import:"utils/eprint" diff --git a/readme.md b/readme.md index f1e01aef..7f886430 100644 --- a/readme.md +++ b/readme.md @@ -18,13 +18,11 @@ I've found other terminal web browsers insufficient for my needs, so I thought it'd be a fun excercise to write one by myself, for myself. The end result will of course not support nearly as many websites as Firefox or -Chromium (so forget PWAs I guess), but I'd like it to be at least somewhat more -functional on the "modern web" than w3m or lynx. +Chromium, but I'd like it to be at least somewhat more functional on the +"modern web" than w3m or lynx. -While the original idea was to implement something similar to w3m's rendering -with JS and minimal CSS support, I've got a bit carried away with my CSS parser -so the new plan is to mostly implement basic CSS stuff and then JS with the -most important APIs. Plus some other things. +The plan is to mostly implement basic CSS stuff and then JS with the most +important APIs. Plus some other things. ## So what can this do? diff --git a/res/config b/res/config deleted file mode 100644 index 0f4e5603..00000000 --- a/res/config +++ /dev/null @@ -1,64 +0,0 @@ -#normal mode keybindings -nmap q QUIT -nmap h CURSOR_LEFT -nmap j CURSOR_DOWN -nmap k CURSOR_UP -nmap l CURSOR_RIGHT -nmap M-[D CURSOR_LEFT -nmap M-[B CURSOR_DOWN -nmap M-[A CURSOR_UP -nmap M-[C CURSOR_RIGHT -nmap ^ CURSOR_LINEBEGIN -nmap $ CURSOR_LINEEND -nmap b CURSOR_PREV_WORD -nmap w CURSOR_NEXT_WORD -nmap [ CURSOR_PREV_LINK -nmap ] CURSOR_NEXT_LINK -nmap H CURSOR_TOP -nmap M CURSOR_MIDDLE -nmap L CURSOR_BOTTOM -nmap C-d HALF_PAGE_DOWN -nmap C-u HALF_PAGE_UP -nmap C-f PAGE_DOWN -nmap C-b PAGE_UP -nmap M-[6~ PAGE_DOWN -nmap M-[5~ PAGE_UP -nmap > PAGE_RIGHT -nmap < PAGE_LEFT -nmap C-e SCROLL_DOWN -nmap C-y SCROLL_UP -nmap J SCROLL_DOWN -nmap K SCROLL_UP -nmap ( SCROLL_LEFT -nmap ) SCROLL_RIGHT -nmap C-m CLICK -nmap C-j CLICK -nmap C-l CHANGE_LOCATION -nmap U RELOAD -nmap r RESHAPE -nmap R REDRAW -nmap gg CURSOR_FIRST_LINE -nmap G CURSOR_LAST_LINE -nmap M-[H CURSOR_FIRST_LINE -nmap M-[F CURSOR_LAST_LINE -nmap z CENTER_LINE -nmap C-g LINE_INFO -nmap gh TOGGLE_SOURCE - -#line editing keybindings -lemap C-m SUBMIT -lemap C-j SUBMIT -lemap C-h BACKSPACE -lemap C-? BACKSPACE -lemap C-d DELETE -lemap C-c CANCEL -lemap M-b PREV_WORD -lemap M-f NEXT_WORD -lemap C-b BACK -lemap C-f FORWARD -lemap C-u CLEAR -lemap C-k KILL -lemap C-w KILL_WORD -lemap C-a BEGIN -lemap C-e END -lemap C-v ESC diff --git a/res/config.toml b/res/config.toml new file mode 100644 index 00000000..520d871a --- /dev/null +++ b/res/config.toml @@ -0,0 +1,63 @@ +[page] +'C-M-j' = 'QUIT' +q = 'QUIT' +h = 'CURSOR_LEFT' +j = 'CURSOR_DOWN' +k = 'CURSOR_UP' +l = 'CURSOR_RIGHT' +'M-[D' = 'CURSOR_LEFT' +'M-[B' = 'CURSOR_DOWN' +'M-[A' = 'CURSOR_UP' +'M-[C' = 'CURSOR_RIGHT' +'^' = 'CURSOR_LINEBEGIN' +'$' = 'CURSOR_LINEEND' +b = 'CURSOR_PREV_WORD' +w = 'CURSOR_NEXT_WORD' +'[' = 'CURSOR_PREV_LINK' +']' = 'CURSOR_NEXT_LINK' +H = 'CURSOR_TOP' +M = 'CURSOR_MIDDLE' +L = 'CURSOR_BOTTOM' +C-d = 'HALF_PAGE_DOWN' +C-u = 'HALF_PAGE_UP' +C-f = 'PAGE_DOWN' +C-b = 'PAGE_UP' +'M-[6~' = 'PAGE_DOWN' +'M-[5~' = 'PAGE_UP' +'>' = 'PAGE_RIGHT' +'<' = 'PAGE_LEFT' +C-e = 'SCROLL_DOWN' +C-y = 'SCROLL_UP' +J = 'SCROLL_DOWN' +K = 'SCROLL_UP' +'('= 'SCROLL_LEFT' +')' = 'SCROLL_RIGHT' +C-m = 'CLICK' +C-j = 'CLICK' +C-l = 'CHANGE_LOCATION' +U = 'RELOAD' +r = 'REDRAW' +R = 'RESHAPE' +g = 'CURSOR_FIRST_LINE' +G = 'CURSOR_LAST_LINE' +z = 'CENTER_LINE' +C-g = 'LINE_INFO' +v = 'TOGGLE_SOURCE' + +[line] +C-m = 'SUBMIT' +C-j = 'SUBMIT' +C-h = 'BACKSPACE' +'C-?' = 'BACKSPACE' +C-d = 'DELETE' +C-c = 'CANCEL' +M-b = 'PREV_WORD' +M-f = 'NEXT_WORD' +C-b = 'BACK' +C-f = 'FORWARD' +C-u = 'CLEAR' +C-k = 'KILL' +C-w = 'KILL_WORD' +C-a = 'BEGIN' +C-e = 'END' +C-v = 'ESC' diff --git a/res/default.css b/res/ua.css index ac09b495..ac09b495 100644 --- a/res/default.css +++ b/res/ua.css diff --git a/src/config/config.nim b/src/config/config.nim index 91b17dda..98d08442 100644 --- a/src/config/config.nim +++ b/src/config/config.nim @@ -1,7 +1,9 @@ import tables import os import strutils +import streams +import config/toml import utils/twtstr import utils/radixtree @@ -33,19 +35,11 @@ type ACTION_LINED_ESC ActionMap = Table[string, TwtAction] - StaticConfig = object - nmap: ActionMap - lemap: ActionMap - stylesheet*: string - Config = object nmap*: ActionMap lemap*: ActionMap stylesheet*: string -func getConfig(s: StaticConfig): Config = - return Config(nmap: s.nmap, lemap: s.lemap) - func getRealKey(key: string): string = var realk: string var control = 0 @@ -106,6 +100,16 @@ func constructActionTable*(origTable: ActionMap): ActionMap = newTable[realk] = v return newTable +func getAction(s: string): TwtAction = + if s == "NULL": + return NO_ACTION + return parseEnum[TwtAction]("ACTION_" & s) + +func getLineAction(s: string): TwtAction = + if s == "NULL": + return NO_ACTION + return parseEnum[TwtAction]("ACTION_LINED_" & s) + proc readUserStylesheet(dir: string, file: string): string = if file.len == 0: return "" @@ -120,75 +124,33 @@ proc readUserStylesheet(dir: string, file: string): string = result = f.readAll() f.close() -proc parseConfigLine[T](line: string, config: var T) = - if line.len == 0 or line[0] == '#': - return - var cmd: seq[string] - var s = "" - var quote = false - var escape = false - for c in line: - if escape: - escape = false - s &= c - continue - - if not quote and c == ' ' and s.len > 0: - cmd.add(s) - s = "" - elif c == '"': - quote = not quote - elif c == '\\' and not quote: - escape = true - else: - s &= c - if s.len > 0: - cmd.add(s) - - if cmd.len == 3: - if cmd[0] == "nmap": - if cmd[2] == "NULL": - config.nmap[getRealKey(cmd[1])] = NO_ACTION - else: - config.nmap[getRealKey(cmd[1])] = parseEnum[TwtAction]("ACTION_" & cmd[2]) - elif cmd[0] == "lemap": - if cmd[2] == "NULL": - config.lemap[getRealKey(cmd[1])] = NO_ACTION - else: - config.lemap[getRealKey(cmd[1])] = parseEnum[TwtAction]("ACTION_LINED_" & cmd[2]) - elif cmd.len == 2: - if cmd[0] == "stylesheet": - config.stylesheet = cmd[1] - -proc staticReadConfig(): StaticConfig = - let default = staticRead"res/config" - for line in default.split('\n'): - parseConfigLine(line, result) - - result.nmap = constructActionTable(result.nmap) - result.lemap = constructActionTable(result.lemap) +func parseConfig(t: TomlValue): Config = + if "page" in t: + for k, v in t["page"].pairs: + result.nmap[getRealKey(k)] = getAction(v.s) + for k, v in t["line"].pairs: + result.nmap[getRealKey(k)] = getLineAction(v.s) + if "stylesheet" in t: + result.stylesheet = t["stylesheet"].s + +proc staticReadConfig(): Config = + result = parseConfig(parseToml(newStringStream(staticRead"res/config.toml"))) + result.stylesheet = readUserStylesheet("res", result.stylesheet) const defaultConfig = staticReadConfig() -var gconfig*: Config +var gconfig* = defaultConfig proc readConfig(dir: string) = - var f: File - let status = f.open(dir / "config", fmRead) - if status: - var line: TaintedString - while f.readLine(line): - parseConfigLine(line, gconfig) - - gconfig.nmap = constructActionTable(gconfig.nmap) - gconfig.lemap = constructActionTable(gconfig.lemap) - gconfig.stylesheet = readUserStylesheet(dir, gconfig.stylesheet) - f.close() - -proc readConfig*() = - gconfig = getConfig(defaultConfig) - when defined(debug): - readConfig(getCurrentDir() / "res") - readConfig(getConfigDir() / "twt") + let fs = newFileStream(dir / "config.toml") + if fs != nil: + let t = parseToml(fs) + if "page" in t: + for k, v in t["page"].pairs: + gconfig.nmap[getRealKey(k)] = getAction(v.s) + for k, v in t["line"].pairs: + gconfig.lemap[getRealKey(k)] = getLineAction(v.s) + if "stylesheet" in t: + gconfig.stylesheet = readUserStylesheet(dir, t["stylesheet"].s) proc getNormalAction*(s: string): TwtAction = if gconfig.nmap.hasKey(s): @@ -200,3 +162,9 @@ proc getLinedAction*(s: string): TwtAction = return gconfig.lemap[s] return NO_ACTION +proc readConfig*() = + when defined(debug): + readConfig(getCurrentDir() / "res") + readConfig(getConfigDir() / "twt") + gconfig.nmap = constructActionTable(gconfig.nmap) + gconfig.lemap = constructActionTable(gconfig.lemap) diff --git a/src/config/toml.nim b/src/config/toml.nim new file mode 100644 index 00000000..cfdb7763 --- /dev/null +++ b/src/config/toml.nim @@ -0,0 +1,406 @@ +import streams +import tables +import times +import strutils +import strformat +import unicode + +import utils/twtstr + +type + ValueType = enum + VALUE_STRING, VALUE_INTEGER, VALUE_FLOAT, VALUE_BOOLEAN, VALUE_DATE_TIME, + VALUE_TABLE, VALUE_ARRAY VALUE_TABLE_ARRAY + + SyntaxError = object of ValueError + + TomlParser = object + at: int + line: int + stream: Stream + buf: string + root: TomlTable + node: TomlNode + currkey: seq[string] + + TomlValue* = ref object + case vt*: ValueType + of VALUE_STRING: + s*: string + of VALUE_INTEGER: + i*: int64 + of VALUE_FLOAT: + f*: float64 + of VALUE_BOOLEAN: + b*: bool + of VALUE_TABLE: + t*: TomlTable + of VALUE_DATE_TIME: + dt*: DateTime + of VALUE_ARRAY: + a*: seq[TomlValue] + of VALUE_TABLE_ARRAY: + ta*: seq[TomlTable] + + TomlNode = ref object of RootObj + comment: string + + TomlKVPair = ref object of TomlNode + key*: seq[string] + value*: TomlValue + + TomlTable = ref object of TomlNode + key: seq[string] + nodes: seq[TomlNode] + map: Table[string, TomlValue] + +func `[]`*(val: TomlValue, key: string): TomlValue = + return val.t.map[key] + +iterator pairs*(val: TomlValue): (string, TomlValue) = + for k, v in val.t.map.pairs: + yield (k, v) + +func contains*(val: TomlValue, key: string): bool = + return key in val.t.map + +func isBare(c: char): bool = + return c == '-' or c == '_' or c.isAlphaNumeric() + +func peek(state: TomlParser, i: int): char = + return state.buf[state.at + i] + +func peek(state: TomlParser, i: int, len: int): string = + return state.buf.substr(state.at + i, state.at + i + len) + +proc syntaxError(state: TomlParser, msg: string) = + raise newException(SyntaxError, fmt"on line {state.line}: {msg}") + +proc valueError(state: TomlParser, msg: string) = + raise newException(ValueError, fmt"on line {state.line}: {msg}") + +proc consume(state: var TomlParser): char = + result = state.buf[state.at] + inc state.at + +proc reconsume(state: var TomlParser) = + dec state.at + +proc has(state: var TomlParser, i: int = 0): bool = + if state.at + i >= state.buf.len and not state.stream.atEnd(): + state.buf &= state.stream.readLine() & '\n' + return state.at + i < state.buf.len + +proc consumeEscape(state: var TomlParser, c: char): Rune = + var len = 4 + if c == 'U': + len = 8 + let c = state.consume() + var num = hexValue(c) + if num != -1: + var i = 0 + while state.has() and i < len: + let c = state.peek(0) + if hexValue(c) == -1: + break + discard state.consume() + num *= 0x10 + num += hexValue(c) + inc i + if i != len - 1: + state.syntaxError(fmt"invalid escaped length ({i}, needs {len})") + if num > 0x10FFFF or num in {0xD800..0xDFFF}: + state.syntaxError(fmt"invalid escaped codepoint {num}") + else: + return Rune(num) + else: + state.syntaxError(fmt"invalid escaped codepoint {c}") + +proc consumeString(state: var TomlParser, first: char): string = + var multiline = false + + if first == '"': + if state.has(1): + let s = state.peek(0, 2) + if s == "\"\"": + multiline = true + elif first == '\'': + if state.has(1): + let s = state.peek(0, 2) + if s == "''": + multiline = true + + if multiline: + let c = state.peek(0) + if c == '\n': + discard state.consume() + + var escape = false + var ml_trim = false + while state.has(): + let c = state.consume() + if c == '\n' and not multiline: + state.syntaxError(fmt"newline in string") + elif c == first: + if multiline and state.has(1): + let c2 = state.peek(0) + let c3 = state.peek(1) + if c2 == first and c3 == first: + break + else: + break + elif first == '"' and c == '\\': + escape = true + elif escape: + case c + of 'b': result &= '\b' + of 't': result &= '\t' + of 'n': result &= '\n' + of 'f': result &= '\f' + of 'r': result &= '\r' + of '"': result &= '"' + of '\\': result &= '\\' + of 'u', 'U': result &= state.consumeEscape(c) + of '\n': ml_trim = true + else: state.syntaxError(fmt"invalid escape sequence \{c}") + escape = false + elif ml_trim: + if not (c in {'\n', ' ', '\t'}): + result &= c + ml_trim = false + else: + result &= c + +proc consumeBare(state: var TomlParser, c: char): string = + result &= c + while state.has(): + let c = state.consume() + case c + of ' ', '\t': break + of '.', '=', ']', '\n': + state.reconsume() + break + elif c.isBare(): + result &= c + else: + state.syntaxError(fmt"invalid value in token: {c}") + +proc flushLine(state: var TomlParser) = + if state.node != nil: + if state.node of TomlKVPair: + var i = 0 + let keys = state.currkey & TomlKVPair(state.node).key + var table = state.root + while i < keys.len - 1: + if keys[i] in table.map: + let node = table.map[keys[i]] + if node.vt != VALUE_TABLE: + let s = keys.join(".") + state.valueError(fmt"re-definition of node {s}") + else: + table = node.t + else: + let node = TomlTable() + table.map[keys[i]] = TomlValue(vt: VALUE_TABLE, t: node) + table = node + inc i + + if keys[i] in table.map: + let s = keys.join(".") + state.valueError(fmt"re-definition of node {s}") + + table.map[keys[i]] = TomlKVPair(state.node).value + table.nodes.add(state.node) + state.node = nil + inc state.line + +proc consumeComment(state: var TomlParser) = + state.node = TomlNode() + while state.has(): + let c = state.consume() + if c == '\n': + state.reconsume() + break + else: + state.node.comment &= c + +proc consumeKey(state: var TomlParser): seq[string] = + var str = "" + while state.has(): + let c = state.consume() + case c + of '"', '\'': + if str.len > 0: + state.syntaxError("multiple strings without dot") + str = state.consumeString(c) + of '=', ']': + if str.len != 0: + result.add(str) + str = "" + return result + of '.': + if str.len == 0: #TODO empty strings are allowed, only empty keys aren't + state.syntaxError("redundant dot") + else: + result.add(str) + str = "" + of ' ', '\t': discard + of '\n': + if state.node != nil: + state.syntaxError("newline without value") + else: + state.flushLine() + elif c.isBare(): + if str.len > 0: + state.syntaxError(fmt"multiple strings without dot: {str}") + str = state.consumeBare(c) + else: state.syntaxError(fmt"invalid character in key: {c}") + + state.syntaxError("key without value") + +proc consumeTable(state: var TomlParser): TomlTable = + new(result) + while state.has(): + let c = state.peek(0) + case c + of ' ', '\t': discard + of '\n': + return result + of '[': + #TODO table array + state.syntaxError("arrays of tables are not supported yet") + of '"', '\'': + result.key = state.consumeKey() + elif c.isBare(): + result.key = state.consumeKey() + else: state.syntaxError(fmt"invalid character before key: {c}") + state.syntaxError("unexpected end of file") + +proc consumeNoState(state: var TomlParser): bool = + while state.has(): + let c = state.peek(0) + case c + of '#', '\n': + return false + of ' ', '\t': discard + of '[': + discard state.consume() + let table = state.consumeTable() + state.currkey = table.key + state.node = table + return false + elif c == '"' or c == '\'' or c.isBare(): + let kvpair = TomlKVPair() + kvpair.key = state.consumeKey() + state.node = kvpair + return true + else: state.syntaxError(fmt"invalid character before key: {c}") + state.syntaxError("unexpected end of file") + +proc consumeNumber(state: var TomlParser): TomlValue = + var repr: string + var isfloat = false + if state.has(): + if state.peek(0) == '+' or state.peek(0) == '-': + repr &= state.consume() + + while state.has() and isDigit(state.peek(0)): + repr &= state.consume() + + if state.has(1): + if state.peek(0) == '.' and isDigit(state.peek(1)): + repr &= state.consume() + repr &= state.consume() + isfloat = true + while state.has() and isDigit(state.peek(0)): + repr &= state.consume() + + if state.has(1): + if state.peek(0) == 'E' or state.peek(0) == 'e': + var j = 2 + if state.peek(1) == '-' or state.peek(1) == '+': + inc j + if state.has(j) and isDigit(state.peek(j)): + while j > 0: + repr &= state.consume() + dec j + + while state.has() and isDigit(state.peek(0)): + repr &= state.consume() + + if isfloat: + let val = parseFloat64(repr) + return TomlValue(vt: VALUE_FLOAT, f: val) + + let val = parseInt64(repr) + return TomlValue(vt: VALUE_INTEGER, i: val) + +proc consumeValue(state: var TomlParser): TomlValue + +proc consumeArray(state: var TomlParser): TomlValue = + result = TomlValue(vt: VALUE_ARRAY) + var val: TomlValue + while state.has(): + let c = state.consume() + case c + of ' ', '\t', '\n': discard + of ']': + if val != nil: + result.a.add(val) + break + of ',': + if val == nil: + state.syntaxError("comma without element") + result.a.add(val) + else: + state.reconsume() + val = state.consumeValue() + +proc consumeValue(state: var TomlParser): TomlValue = + while state.has(): + let c = state.consume() + case c + of '"', '\'': + return TomlValue(vt: VALUE_STRING, s: state.consumeString(c)) + of ' ', '\t': discard + of '\n': + state.syntaxError("newline without value") + of '#': + state.syntaxError("comment without value") + of '+', '-', '0'..'9': + return state.consumeNumber() + #TODO date-time + of '[': + return state.consumeArray() + elif c.isBare(): + let s = state.consumeBare(c) + case s + of "true": return TomlValue(vt: VALUE_BOOLEAN, b: true) + of "false": return TomlValue(vt: VALUE_BOOLEAN, b: false) + else: state.syntaxError(fmt"invalid token {s}") + else: + state.syntaxError(fmt"invalid character in value: {c}") + +proc parseToml*(inputStream: Stream): TomlValue = + var state: TomlParser + state.stream = inputStream + state.line = 1 + state.root = TomlTable() + + while state.has(): + if state.consumeNoState(): + let kvpair = TomlKVPair(state.node) + kvpair.value = state.consumeValue() + + while state.has(): + let c = state.consume() + case c + of '\n': + state.flushLine() + break + of '#': + state.consumeComment() + of '\t', ' ': discard + else: state.syntaxError(fmt"invalid character after value: {c}") + + return TomlValue(vt: VALUE_TABLE, t: state.root) diff --git a/src/css/parser.nim b/src/css/parser.nim index ec2b6591..0c1f91cc 100644 --- a/src/css/parser.nim +++ b/src/css/parser.nim @@ -1,6 +1,3 @@ -# CSS tokenizer and parser. It kinda works, though certain less specific -# details of the specification might have been implemented incorrectly. - import unicode import streams import math @@ -73,49 +70,6 @@ type func `==`*(a: CSSParsedItem, b: CSSTokenType): bool = return a of CSSToken and CSSToken(a).tokenType == b -func toNumber(s: seq[Rune]): float64 = - var sign = 1 - var t = 1 - var d = 0 - var integer: float64 = 0 - var f: float64 = 0 - var e: float64 = 0 - - var i = 0 - if i < s.len and s[i] == Rune('-'): - sign = -1 - inc i - elif i < s.len and s[i] == Rune('+'): - inc i - - while i < s.len and isDigitAscii(s[i]): - integer *= 10 - integer += float64(decValue(s[i])) - inc i - - if i < s.len and s[i] == Rune('.'): - inc i - while i < s.len and isDigitAscii(s[i]): - f *= 10 - f += float64(decValue(s[i])) - inc i - inc d - - if i < s.len and (s[i] == Rune('e') or s[i] == Rune('E')): - inc i - if i < s.len and s[i] == Rune('-'): - t = -1 - inc i - elif i < s.len and s[i] == Rune('+'): - inc i - - while i < s.len and isDigitAscii(s[i]): - e *= 10 - e += float64(decValue(s[i])) - inc i - - return float64(sign) * (integer + f * pow(10, float64(-d))) * pow(10, (float64(t) * e)) - func isNameStartCodePoint*(r: Rune): bool = return not isAscii(r) or r == Rune('_') or isAlphaAscii(r) @@ -273,7 +227,7 @@ proc consumeNumber(state: var CSSTokenizerState): tuple[t: tflagb, val: float64] while state.has() and isDigitAscii(state.curr()): repr &= state.consume() - let val = toNumber(repr) + let val = parseFloat64($repr) return (t, val) proc consumeNumericToken(state: var CSSTokenizerState): CSSToken = diff --git a/src/io/buffer.nim b/src/io/buffer.nim index 36700e12..18e4a3d6 100644 --- a/src/io/buffer.nim +++ b/src/io/buffer.nim @@ -719,7 +719,7 @@ proc renderPlainText*(buffer: Buffer, text: string) = buffer.updateCursor() -const css = staticRead"res/default.css" +const css = staticRead"res/ua.css" let ua_stylesheet = newStringStream(css).parseStylesheet() #TODO refactor diff --git a/src/io/pipe.nim b/src/io/pipe.nim new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/io/pipe.nim diff --git a/src/utils/twtstr.nim b/src/utils/twtstr.nim index 2dd8615b..d437be6a 100644 --- a/src/utils/twtstr.nim +++ b/src/utils/twtstr.nim @@ -5,6 +5,7 @@ import tables import json import bitops import os +import math when defined(posix): import posix @@ -40,15 +41,6 @@ func fitValueToSize*(str: string, size: int): string = return str & ' '.repeat(size - str.runeLen) return str.maxString(size) -func buttonFmt*(str: string): seq[string] = - return "[".ansiFgColor(fgRed) & str.ansiFgColor(fgRed).ansiReset() & "]".ansiFgColor(fgRed).ansiReset() - -func buttonFmt*(str: seq[string]): seq[string] = - return "[".ansiFgColor(fgRed) & str.ansiFgColor(fgRed).ansiReset() & "]".ansiFgColor(fgRed).ansiReset() - -func buttonRaw*(str: string): string = - return "[" & str & "]" - func remove*(str: string, c: string): string = let rem = c.toRunes()[0] for rune in str.runes: @@ -198,6 +190,83 @@ func skipBlanks*(buf: string, at: int): int = while result < buf.len and buf[result].isWhitespace(): inc result +func parseInt64*(s: string): int64 = + var sign = 1 + var t = 1 + var d = 0 + var integer: int64 = 0 + var e: int64 = 0 + + var i = 0 + if i < s.len and s[i] == '-': + sign = -1 + inc i + elif i < s.len and s[i] == '+': + inc i + + while i < s.len and isDigit(s[i]): + integer *= 10 + integer += decValue(s[i]) + inc i + + if i < s.len and (s[i] == 'e' or s[i] == 'E'): + inc i + if i < s.len and s[i] == '-': + t = -1 + inc i + elif i < s.len and s[i] == '+': + inc i + + while i < s.len and isDigit(s[i]): + e *= 10 + e += decValue(s[i]) + inc i + + return sign * integer * 10 ^ t * e + +func parseFloat64*(s: string): float64 = + var sign = 1 + var t = 1 + var d = 0 + var integer: float64 = 0 + var f: float64 = 0 + var e: float64 = 0 + + var i = 0 + if i < s.len and s[i] == '-': + sign = -1 + inc i + elif i < s.len and s[i] == '+': + inc i + + while i < s.len and isDigit(s[i]): + integer *= 10 + integer += float64(decValue(s[i])) + inc i + + if i < s.len and s[i] == '.': + inc i + while i < s.len and isDigit(s[i]): + f *= 10 + f += float64(decValue(s[i])) + inc i + inc d + + if i < s.len and (s[i] == 'e' or s[i] == 'E'): + inc i + if i < s.len and s[i] == '-': + t = -1 + inc i + elif i < s.len and s[i] == '+': + inc i + + while i < s.len and isDigit(s[i]): + e *= 10 + e += float64(decValue(s[i])) + inc i + + return float64(sign) * (integer + f * pow(10, float64(-d))) * pow(10, (float64(t) * e)) + proc expandPath*(path: string): string = if path.len == 0: return path @@ -599,4 +668,3 @@ proc fullwidth*(s: seq[Rune]): seq[Rune] = proc fullwidth*(s: string): string = return $fullwidth(s.toRunes()) - |