; a simple line editor for reading lisp expressions. ; colors strings and comments. nested parens get different colors. ; ; needs to do its own raw keyboard/screen management since we need to decide ; how to color each key right as it is printed. ; lots of logic devoted to handling backspace correctly. ; keyboard screen abort continuation -> string (function read-expression [ (default-space:space-address <- new space:literal 60:literal) (k:keyboard-address <- next-input) (screen:terminal-address <- next-input) (abort:continuation <- next-input) (history:buffer-address <- next-input) ; buffer of strings (history-length:integer <- get history:buffer-address/deref length:offset) (current-history-index:integer <- copy history-length:integer) (result:buffer-address <- init-buffer 10:literal) ; string to maybe add to (open-parens:integer <- copy 0:literal) ; for balancing parens and tracking nesting depth ; we can change color when backspacing over parens or comments or strings, ; but we need to know that they aren't escaped (escapes:buffer-address <- init-buffer 5:literal) ; to not return after just a comment (not-empty?:boolean <- copy nil:literal) { begin ; repeatedly read keys from the keyboard ; test: 34 (done?:boolean <- process-key default-space:space-address k:keyboard-address screen:terminal-address) (loop-unless done?:boolean) } ; trim trailing newline in result (easier history management below) { begin (l:character <- last result:buffer-address) (trailing-newline?:boolean <- equal l:character ((#\newline literal))) (break-unless trailing-newline?:boolean) (len:integer-address <- get-address result:buffer-address/deref length:offset) (len:integer-address/deref <- subtract len:integer-address/deref 1:literal) } ; test: 3 => size of s is 2 (s:string-address <- to-array result:buffer-address) (reply s:string-address) ]) (function process-key [ ; return t to signal end of expression (default-space:space-address <- new space:literal 60:literal) (0:space-address/names:read-expression <- next-input) (k:keyboard-address <- next-input) (screen:terminal-address <- next-input) (c:character <- wait-for-key k:keyboard-address silent:literal/terminal) (len:integer-address <- get-address result:buffer-address/space:1/deref length:offset) (maybe-cancel-this-expression c:character abort:continuation/space:1) ; check for ctrl-d and exit { begin (eof?:boolean <- equal c:character ((ctrl-d literal))) (break-unless eof?:boolean) ; return empty expression (s:string-address-address <- get-address result:buffer-address/space:1/deref data:offset) (s:string-address-address/deref <- copy nil:literal) (reply t:literal) } ; check for backspace ; test: 34 ; todo: backspace past newline { begin (backspace?:boolean <- equal c:character ((#\backspace literal))) (break-unless backspace?:boolean) (print-character screen:terminal-address c:character/backspace) { begin ; delete last character if any (zero?:boolean <- lesser-or-equal len:integer-address/deref 0:literal) (break-if zero?:boolean) (len:integer-address/deref <- subtract len:integer-address/deref 1:literal) ; switch colors ; test: "a"bc" ; test: "a\"bc" { begin (backspaced-over-close-quote?:boolean <- backspaced-over-unescaped? result:buffer-address/space:1 ((#\" literal)) escapes:buffer-address/space:1) ; " (break-unless backspaced-over-close-quote?:boolean) (slurp-string result:buffer-address/space:1 escapes:buffer-address/space:1 abort:continuation/space:1 k:keyboard-address screen:terminal-address) (reply nil:literal) } ; test: (+ 1 (2) ; test: (+ 1 #\(2) { begin (backspaced-over-open-paren?:boolean <- backspaced-over-unescaped? result:buffer-address/space:1 ((#\( literal)) escapes:buffer-address/space:1) (break-unless backspaced-over-open-paren?:boolean) (open-parens:integer/space:1 <- subtract open-parens:integer/space:1 1:literal) (reply nil:literal) } ; test: (+ 1 2) 3) ; test: (+ 1 2#\) 3) { begin (backspaced-over-close-paren?:boolean <- backspaced-over-unescaped? result:buffer-address/space:1 ((#\) literal)) escapes:buffer-address/space:1) (break-unless backspaced-over-close-paren?:boolean) (open-parens:integer/space:1 <- add open-parens:integer/space:1 1:literal) (reply nil:literal) } } (reply nil:literal) } ; up arrow; switch to previous item in history { begin (up-arrow?:boolean <- equal c:character ((up literal))) (break-unless up-arrow?:boolean) ; if history exists ; test: up without history has no effect { begin (empty-history?:boolean <- lesser-or-equal history-length:integer/space:1 0:literal) (break-unless empty-history?:boolean) (reply nil:literal) } ; if pointer not already at start of history ; test: 34 up past history has no effect { begin (at-history-start?:boolean <- lesser-or-equal current-history-index:integer/space:1 0:literal) (break-unless at-history-start?:boolean) (reply nil:literal) } ; then update history index, copy into current buffer ; test: 34 up restores previous command ; test todo: 342334 up doesn't mess up typing on current line ; test todo: 345 commands don't modify history ; test todo: multi-line expressions ; identify the history item (current-history-index:integer/space:1 <- subtract current-history-index:integer/space:1 1:literal) (switch-to-history 0:space-address screen:terminal-address) ; is trimmed in the history expression, so wait for the human to ; hit again or backspace to make edits (reply nil:literal) } ; down arrow; switch to next item in history { begin (down-arrow?:boolean <- equal c:character ((down literal))) (break-unless down-arrow?:boolean) ; if history exists ; test: down without history has no effect { begin (empty-history?:boolean <- lesser-or-equal history-length:integer/space:1 0:literal) (break-unless empty-history?:boolean) (reply nil:literal) } ; if pointer not already at end of history ; test: 34 up past history has no effect { begin (x:integer <- subtract history-length:integer/space:1 1:literal) (before-history-end?:boolean <- greater-or-equal current-history-index:integer/space:1 x:integer) (break-unless before-history-end?:boolean) (reply nil:literal) } ; then update history index, copy into current buffer ; test: 34 up restores previous command ; test todo: 342334 up doesn't mess up typing on current line ; test todo: 345 commands don't modify history ; test todo: multi-line expressions ; identify the history item (current-history-index:integer/space:1 <- add current-history-index:integer/space:1 1:literal) (switch-to-history 0:space-address screen:terminal-address) ; is trimmed in the history expression, so wait for the human to ; hit again or backspace to make edits (reply nil:literal) } ; if it's a newline, decide whether to return ; test: 34 { begin (newline?:boolean <- equal c:character ((#\newline literal))) (break-unless newline?:boolean) (print-character screen:terminal-address c:character/newline) (at-top-level?:boolean <- lesser-or-equal open-parens:integer/space:1 0:literal) (end-expression?:boolean <- and at-top-level?:boolean not-empty?:boolean/space:1) (reply end-expression?:boolean) } ; printable character; save ;? ($print (("append\n" literal))) ;? 2 (result:buffer-address/space:1 <- append result:buffer-address/space:1 c:character) ;? ($print (("done\n" literal))) ;? 2 ; if it's backslash, read, save and print one additional character ; test: (prn #\() { begin (backslash?:boolean <- equal c:character ((#\\ literal))) (break-unless backslash?:boolean) (print-character screen:terminal-address c:character/backslash 7:literal/white) (result:buffer-address/space:1 escapes:buffer-address/space:1 <- slurp-escaped-character result:buffer-address/space:1 7:literal/white escapes:buffer-address/space:1 abort:continuation/space:1 k:keyboard-address screen:terminal-address) (reply nil:literal) } ; if it's a semi-colon, parse a comment { begin (comment?:boolean <- equal c:character ((#\; literal))) (break-unless comment?:boolean) (print-character screen:terminal-address c:character/semi-colon 4:literal/fg/blue) (comment-read?:boolean <- slurp-comment result:buffer-address/space:1 escapes:buffer-address/space:1 abort:continuation/space:1 k:keyboard-address screen:terminal-address) ; return if comment was read (i.e. consumed a newline) ; test: ;a (shouldn't end command until ) { begin (break-if comment-read?:boolean) (reply nil:literal) } ; and we're not within parens ; test: (+ 1 2) ; comment ; test: (+ 1; abc2) ; test: ; comment(+ 1 2) ; too expensive to build: 3; comment(+ 1 2)pre { line-height: 125%; } td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } .highlight .hll { background-color: #ffffcc } .highlight .c { color: #888888 } /* Comment */ .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ .highlight .k { color: #008800; font-weight: bold } /* Keyword */ .highlight .ch { color: #888888 } /* Comment.Hashbang */ .highlight .cm { color: #888888 } /* Comment.Multiline */ .highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ .highlight .cpf { color: #888888 } /* Comment.PreprocFile */ .highlight .c1 { color: #888888 } /* Comment.Single */ .highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #333333 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
#
#
#            Nimrod's Runtime Library
#        (c) Copyright 2012 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## A higher level `PostgreSQL`:idx: database wrapper. This interface 
## is implemented for other databases too.

import strutils, postgres

type
  TDbConn* = PPGconn   ## encapsulates a database connection
  TRow* = seq[string]  ## a row of a dataset. NULL database values will be
                       ## transformed always to the empty string.
  EDb* = object of EIO ## exception that is raised if a database error occurs
  
  TSqlQuery* = distinct string ## an SQL query string

  FDb* = object of FIO ## effect that denotes a database operation
  FReadDb* = object of FDB   ## effect that denotes a read operation
  FWriteDb* = object of FDB  ## effect that denotes a write operation
  
proc sql*(query: string): TSqlQuery {.noSideEffect, inline.} =  
  ## constructs a TSqlQuery from the string `query`. This is supposed to be 
  ## used as a raw-string-literal modifier:
  ## ``sql"update user set counter = counter + 1"``
  ##
  ## If assertions are turned off, it does nothing. If assertions are turned 
  ## on, later versions will check the string for valid syntax.
  result = TSqlQuery(query)
 
proc dbError(db: TDbConn) {.noreturn.} = 
  ## raises an EDb exception.
  var e: ref EDb
  new(e)
  e.msg = $PQerrorMessage(db)
  raise e

proc dbError*(msg: string) {.noreturn.} = 
  ## raises an EDb exception with message `msg`.
  var e: ref EDb
  new(e)
  e.msg = msg
  raise e

proc dbQuote(s: string): string =
  result = "'"
  for c in items(s):
    if c == '\'': add(res