c24595436f9b9febcee97c8807bcc6ebe60bd511 — octaspire 13 days ago v0.473.0
Add new Nuklear IRC client example, make REPL to accept -I --include

* Refactor IRC client example by moving all IRC related
  Dern code into a separate source (.dern) library.

  This library is then used in the ncurses IRC client
  and in the new Nuklear IRC client examples.

* Make the Dern REPL to accept '-I dir' and '--include dir'
  command line arguments for setting/adding include directories
  where source (.dern) libraries are going to be searched for.

  This is needed when running the IRC client examples
  from outside the 'examples'-directory, because the
  new IRC library is located in the 'examples'-directory
  and not in the build directory with the binary plugins.
M GNUmakefile => GNUmakefile +7 -3
@@ 10,6 10,7 @@ CORDIR=$(EXTDIR)octaspire_core/release/
  RELDIR=release/
  GAMESDIR=$(RELDIR)games/
+ EXAMPLESDIR=$(RELDIR)examples/
  PLUGINDIR=$(RELDIR)plugins/
  RELDOCDIR=$(RELDIR)documentation/
  AMALGAMATION=$(RELDIR)octaspire-dern-amalgamated.c


@@ 23,9 24,12 @@ $(CORDIR)octaspire-core-amalgamated.c \
                  $(PLUGINDIR)external/nuklear/nuklear.h
  
- TAGS_DERN_FILES := $(GAMESDIR)octaspire-bounce.dern    \
-                    $(GAMESDIR)octaspire-lightcube.dern \
-                    $(GAMESDIR)octaspire-maze.dern
+ TAGS_DERN_FILES := $(GAMESDIR)octaspire-bounce.dern      \
+                    $(GAMESDIR)octaspire-lightcube.dern   \
+                    $(GAMESDIR)octaspire-maze.dern        \
+                    $(EXAMPLESDIR)dern_irc.dern           \
+                    $(EXAMPLESDIR)irc-client-nuklear.dern \
+                    $(EXAMPLESDIR)irc-client-ncurses.dern
  
  CHIPMUNK_SRCS := $(wildcard release/plugins/external/chipmunk/src/*.c)
  CHIPMUNK_OBJS := $(patsubst %.c, %.o, $(CHIPMUNK_SRCS))

M dev/etc/amalgamation_head.c => dev/etc/amalgamation_head.c +1 -0
@@ 64,6 64,7 @@ #include <limits.h>
  #include <wchar.h>
  #include <locale.h>
+ #include <errno.h>
  
  #endif
  

M dev/include/octaspire/dern/octaspire_dern_config.h => dev/include/octaspire/dern/octaspire_dern_config.h +1 -1
@@ 18,7 18,7 @@ #define OCTASPIRE_DERN_CONFIG_H
  
  #define OCTASPIRE_DERN_CONFIG_VERSION_MAJOR "0"
- #define OCTASPIRE_DERN_CONFIG_VERSION_MINOR "472"
+ #define OCTASPIRE_DERN_CONFIG_VERSION_MINOR "473"
  #define OCTASPIRE_DERN_CONFIG_VERSION_PATCH "0"
  
  #define OCTASPIRE_DERN_CONFIG_VERSION_STR "Octaspire Dern version " \

M dev/include/octaspire/dern/octaspire_dern_vm.h => dev/include/octaspire/dern/octaspire_dern_vm.h +1 -0
@@ 48,6 48,7 @@ octaspire_dern_vm_custom_require_source_file_loader_t preLoaderForRequireSrc;
      bool debugModeOn;
      bool noDlClose;
+     octaspire_vector_t * includeDirectories;
  }
  octaspire_dern_vm_config_t;
  

M dev/src/octaspire_dern_repl.c => dev/src/octaspire_dern_repl.c +56 -7
@@ 188,6 188,7 @@ "If any of -e string or [file] is used, REPL is not started unless -i is used.\n\n"
          "-c        --color-diagnostics : use colors on unix like systems\n"
          "-i        --interactive       : start REPL after any -e string or [file]s are evaluated\n"
+         "-I dir    --include dir       : Search this directory for source (.dern) libraries\n"
          "-e string --evaluate string   : evaluate a string without entering the REPL (see -i)\n"
          "-v        --version           : print version information and exit\n"
          "-h        --help              : print this help message and exit\n"


@@ 201,19 202,23 @@   
  // Globals for the REPL. ////////////////////////////
- static octaspire_vector_t      *stringsToBeEvaluated = 0;
- static octaspire_allocator_t      *allocatorBootOnly    = 0;
- static octaspire_string_t *line                 = 0;
- static octaspire_stdio_t                 *stdio                = 0;
- static octaspire_input_t                 *input                = 0;
- static octaspire_dern_vm_t               *vm                   = 0;
- static octaspire_allocator_t      *allocator            = 0;
+ static octaspire_vector_t    *stringsToBeEvaluated = 0;
+ static octaspire_vector_t    *includeDirectories   = 0;
+ static octaspire_allocator_t *allocatorBootOnly    = 0;
+ static octaspire_string_t    *line                 = 0;
+ static octaspire_stdio_t     *stdio                = 0;
+ static octaspire_input_t     *input                = 0;
+ static octaspire_dern_vm_t   *vm                   = 0;
+ static octaspire_allocator_t *allocator            = 0;
  
  static void octaspire_dern_repl_private_cleanup(void)
  {
      octaspire_vector_release(stringsToBeEvaluated);
      stringsToBeEvaluated = 0;
  
+     octaspire_vector_release(includeDirectories);
+     includeDirectories = 0;
+ 
      octaspire_allocator_release(allocatorBootOnly);
      allocatorBootOnly = 0;
  


@@ 249,6 254,7 @@ int  userFilesStartIdx       = -1;
      bool enterReplAlways         = false;
      bool evaluate                = false;
+     bool include                 = false;
  
      octaspire_dern_vm_config_t vmConfig = octaspire_dern_vm_config_default();
  


@@ 297,6 303,23 @@ exit(EXIT_FAILURE);
      }
  
+     includeDirectories = octaspire_vector_new(
+         sizeof(octaspire_string_t*),
+         true,
+         (octaspire_vector_element_callback_t)octaspire_string_release,
+         allocatorBootOnly);
+ 
+     if (!includeDirectories)
+     {
+         octaspire_dern_repl_print_message_c_str(
+             "Cannot create include directory vector",
+             OCTASPIRE_DERN_REPL_MESSAGE_FATAL,
+             useColors,
+             0);
+ 
+         exit(EXIT_FAILURE);
+     }
+ 
      if (argc > 1)
      {
          for (int i = 1; i < argc; ++i)


@@ 322,6 345,28 @@                   octaspire_vector_push_back_element(stringsToBeEvaluated, &tmp);
              }
+             else if (include)
+             {
+                 include = false;
+ 
+                 octaspire_string_t *tmp = octaspire_string_new(
+                     argv[i],
+                     allocatorBootOnly);
+ 
+                 if (!tmp)
+                 {
+                     octaspire_dern_repl_print_message_c_str(
+                         "Cannot create string for include path",
+                         OCTASPIRE_DERN_REPL_MESSAGE_FATAL,
+                         useColors,
+                         0);
+ 
+                     exit(EXIT_FAILURE);
+                 }
+ 
+                 octaspire_vector_push_back_element(includeDirectories, &tmp);
+                 vmConfig.includeDirectories = includeDirectories;
+             }
              else if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--color-diagnostics") == 0)
              {
                  useColors = true;


@@ 330,6 375,10 @@ {
                  enterReplAlways = true;
              }
+             else if (strcmp(argv[i], "-I") == 0 || strcmp(argv[i], "--include") == 0)
+             {
+                 include = true;
+             }
              else if (strcmp(argv[i], "-e") == 0 || strcmp(argv[i], "--evaluate") == 0)
              {
                  evaluate = true;

M dev/src/octaspire_dern_stdlib.c => dev/src/octaspire_dern_stdlib.c +82 -5
@@ 16,6 16,7 @@ ******************************************************************************/
  #include "octaspire/dern/octaspire_dern_stdlib.h"
  #include <assert.h>
+ #include <errno.h>
  #include <inttypes.h>
  #include <math.h>
  


@@ 30,6 31,7 @@   #include "octaspire/dern/octaspire_dern_vm.h"
  #include "octaspire/dern/octaspire_dern_config.h"
+ #include "octaspire/dern/octaspire_dern_port.h"
  
  #ifdef OCTASPIRE_DERN_CONFIG_BINARY_PLUGINS
  #include <dlfcn.h>


@@ 3589,10 3591,23 @@ "Builtin 'io-file-open' expects string argument.");
      }
  
-     octaspire_dern_value_t * const result = octaspire_dern_vm_create_new_value_io_file(
+     octaspire_dern_value_t * result = octaspire_dern_vm_create_new_value_io_file(
          vm,
          octaspire_dern_value_as_string_get_c_string(firstArg));
  
+     octaspire_helpers_verify_not_null(result);
+     octaspire_helpers_verify_not_null(result->value.port);
+ 
+     if (!octaspire_dern_port_supports_output(result->value.port) ||
+         !octaspire_dern_port_supports_input(result->value.port))
+     {
+         result = octaspire_dern_vm_create_new_value_error_format(
+             vm,
+             "Builtin 'io-file-open' failed to open file '%s': %s.",
+             octaspire_dern_value_as_string_get_c_string(firstArg),
+             strerror(errno));
+     }
+ 
      octaspire_dern_vm_pop_value(vm, arguments);
      octaspire_helpers_verify_true(stackLength == octaspire_dern_vm_get_stack_length(vm));
      return result;


@@ 8998,10 9013,58 @@ }
      else
      {
-         input = octaspire_input_new_from_path(
-             octaspire_string_get_c_string(fileName),
-             octaspire_dern_vm_get_allocator(vm),
-             octaspire_dern_vm_get_stdio(vm));
+         octaspire_dern_vm_config_t const * const config =
+             octaspire_dern_vm_get_config_const(vm);
+ 
+         octaspire_helpers_verify_not_null(config);
+ 
+         if (config->includeDirectories)
+         {
+             for (size_t i = 0;
+                  i < octaspire_vector_get_length(config->includeDirectories);
+                  ++i)
+             {
+                 octaspire_string_t const * const str =
+                     octaspire_vector_get_element_at_const(config->includeDirectories, i);
+ 
+                 octaspire_helpers_verify_not_null(str);
+ 
+ #ifdef _WIN32
+                 char const * const pathSeparator = "\\";
+ #else
+                 char const * const pathSeparator = "/";
+ #endif
+ 
+                 octaspire_string_t * newPath = octaspire_string_new_format(
+                     octaspire_dern_vm_get_allocator(vm),
+                     "%s%s%s",
+                     octaspire_string_get_c_string(str),
+                     pathSeparator,
+                     octaspire_string_get_c_string(fileName));
+ 
+                 octaspire_helpers_verify_not_null(newPath);
+ 
+                 input = octaspire_input_new_from_path(
+                     octaspire_string_get_c_string(newPath),
+                     octaspire_dern_vm_get_allocator(vm),
+                     octaspire_dern_vm_get_stdio(vm));
+ 
+                 octaspire_string_release(newPath);
+                 newPath = 0;
+ 
+                 if (input)
+                 {
+                     break;
+                 }
+             }
+         }
+         else
+         {
+             input = octaspire_input_new_from_path(
+                 octaspire_string_get_c_string(fileName),
+                 octaspire_dern_vm_get_allocator(vm),
+                 octaspire_dern_vm_get_stdio(vm));
+         }
      }
  
      if (!input)


@@ 9763,6 9826,20 @@ }
  
          case OCTASPIRE_DERN_VALUE_TAG_NIL:
+         {
+             octaspire_helpers_verify_true(
+                 stackLength == octaspire_dern_vm_get_stack_length(vm));
+ 
+             if (numArgs > 2)
+             {
+                 return octaspire_dern_vm_create_new_value_error_from_c_string(
+                     vm,
+                     "Builtin 'cp@' expects exactly one argument when used with nil.");
+             }
+ 
+             return octaspire_dern_vm_create_new_value_nil(vm);
+         }
+ 
          case OCTASPIRE_DERN_VALUE_TAG_BOOLEAN:
          case OCTASPIRE_DERN_VALUE_TAG_INTEGER:
          case OCTASPIRE_DERN_VALUE_TAG_REAL:

M dev/src/octaspire_dern_vm.c => dev/src/octaspire_dern_vm.c +2 -1
@@ 87,7 87,8 @@ {
          .preLoaderForRequireSrc  = 0,
          .debugModeOn             = false,
-         .noDlClose               = false
+         .noDlClose               = false,
+         .includeDirectories      = 0
      };
  
      return result;

A release/examples/dern_irc.dern => release/examples/dern_irc.dern +219 -0
@@ 0,0 1,219 @@
+ (require 'dern_socket)
+ 
+ (define irc-current-channel   as nil     [currently joined channel])
+ (define irc-current-username  as []      [username])
+ (define irc-current-realname  as []      [realname])
+ (define irc-current-hint      as []      [current info or error message])
+ (define irc-line-length       as {D+70}  [length of message line])
+ (define irc-socket            as nil     [socket] in (env-global))
+ (define irc-running           as true    [becomes false on /quit])
+ 
+ (define irc-info-type? as (fn (type)
+   (or (== type 'ERROR) (== type 'INFO) (== type 'SUCCESS)))
+   [is given type information] '(type [type]) howto-ok)
+ 
+ (define irc-break-into-lines as (fn (message)
+   (define result as (vector) [result])
+   (define line   as [] [a line])
+   (for c in message
+     (+= line c)
+     (if (>= (len line) irc-line-length) (do
+       (+= result (copy line))
+       (= line []))))
+   (if (or (== (len result) {D+0}) (> (len line) {D+0})) (+= result (copy line)))
+   (return result))
+   [break message into lines] '(message [message to break]) howto-ok)
+ 
+ (define irc-create-result as (fn (type message)
+   (define result as (vector) [result vector])
+   (+= result type)
+   (+= result message)
+   (+= result (irc-break-into-lines message))
+   (if (irc-info-type? type)
+     (= irc-current-hint message))
+   (return result))
+   [create result message] '(type [type] message [message]) howto-ok)
+ 
+ (define irc-set-current-channel as (fn (channel)
+   (= irc-current-channel channel))
+   [set current channel] '(channel [channel]) howto-no)
+ 
+ (define irc-leave-current-channel as (fn ()
+   (define message as
+     (string-format [PART {}|newline|] irc-current-channel)
+     [message to be send])
+   (socket-send irc-socket message)
+   (irc-set-current-channel nil)
+   (irc-create-result 'SUCCESS [OK]))
+   [leave the current channel] '() howto-no)
+ 
+ (define substr as (fn (str startIndex)
+   (define result as [] [result])
+   (if (>= startIndex (len str)) (return result))
+   (for i from startIndex to (- (len str) {D+1})
+     (+= result (cp@ str i)))
+   result)
+   [substr] '(str [str] startIndex [startIndex]) howto-no)
+ 
+ (define irc-handle-nick as (fn (line)
+   (= irc-current-username (substr line {D+6}))
+   (return (irc-send-user-data)))
+   [handle /nick] '(line [line of text starting with /nick]) howto-no)
+ 
+ (define irc-handle-join as (fn (line)
+   (if (== irc-current-username [])
+     (return (irc-create-result 'ERROR [Please set a nick first by using /nick or /user])))
+   (define channel as (substr line {D+6}) [channel])
+   (define message as (string-format [JOIN {}|newline|] channel) [message])
+   (socket-send irc-socket message)
+   (irc-set-current-channel channel)
+   (irc-create-result 'SUCCESS (string-format [joined {}] irc-current-channel)))
+   [handle /join] '(line [line of text starting with /join]) howto-no)
+ 
+ (define irc-handle-msg as (fn (line)
+     (define restOfLine as (substr line {D+5}) [line without /msg])
+ 
+     (define tokens as (split restOfLine | |) [line split by space])
+ 
+     (if (< (len tokens) {D+2}) (return
+       (irc-create-result 'ERROR [/msg expect target name and message])))
+ 
+     (define target as (ln@ tokens {D+0}) [target] )
+     (define msgtxt as [] [msgtxt])
+ 
+     (for i from {D+1} to (- (len tokens) {D+1})
+          (+= msgtxt (ln@ tokens i))
+          (if (< i (- (len tokens) {D+1})) (+= msgtxt | |)))
+ 
+     (define message as (string-format [PRIVMSG {} :{}|newline|] target msgtxt) [message])
+     (socket-send irc-socket message)
+     (irc-create-result 'PRIVATE-MESSAGE (string-format [<---------------You---------------> {}] msgtxt)))
+   [handle /msg] '(line [line of text starting with /msg]) howto-no)
+ 
+ (define irc-handle-connect as (fn (line)
+   (if (== irc-current-username [])
+     (return (irc-create-result
+       'ERROR [Please set a nick first by using /nick or nick and a real name using /user])))
+   (define restOfLine as (substr line {D+9})        [line without /connect])
+   (define tokens     as (split restOfLine | |)     [line split by space] )
+   (define address    as [localhost]                [address])
+   (define portnum    as {D+6667}                   [portnum])
+   (if (>= (len tokens) {D+1}) (= address (ln@ tokens {D+0})))
+   (if (>= (len tokens) {D+2}) (= portnum (ln@ tokens {D+1})))
+   (= portnum (to-integer portnum))
+   ; Sockets cannot be copied with '='
+   (define irc-socket as (socket-new-ipv4-stream-socket address portnum) [socket] in (env-global))
+   (if (string? irc-socket)
+     (do
+       (define error-message as (copy irc-socket) [error message])
+       (= irc-socket nil)
+       (return (irc-create-result 'ERROR error-message))))
+   (irc-read-socket)
+   (irc-send-user-data)
+   (irc-read-socket)
+   (return (irc-create-result 'SUCCESS (string-format [connected to {} at port {}] address portnum))))
+   [handle /connect] '(line [line of text starting with /connect]) howto-no)
+ 
+ (define irc-send-user-data as (fn ()
+   (if (== irc-socket nil) (return (irc-create-result 'ERROR [No connection])))
+   (socket-send irc-socket (string-format [NICK {}|newline|] irc-current-username))
+   (socket-send irc-socket (string-format
+     [USER {} 8 * :{}|newline|]
+     irc-current-username
+     irc-current-realname))
+   (irc-create-result 'SUCCESS [OK]))
+   [send nick and possible real name to server] '() howto-no)
+ 
+ (define irc-handle-user as (fn (line)
+   (define restOfLine as (substr line {D+6}) [line without /user])
+   (define tokens as (split restOfLine | |) [line split by space])
+   (= irc-current-username (cp@ tokens {D+0}))
+   (= irc-current-realname [])
+   (if (> (len tokens) {D+1})
+     (for i from {D+1} to (- (len tokens) {D+1})
+       (+= irc-current-realname (cp@ tokens i))
+       (if (< i (- (len tokens) {D+1})) (+= irc-current-realname | |))))
+   (return (irc-send-user-data)))
+   [handle /user] '(line [line of text starting with /user]) howto-no)
+ 
+ (define irc-handle-command as (fn (line)
+   (if (starts-with? line [/quit])    (do (= irc-running false) (return (irc-create-result 'SUCCESS [QUIT OK]))))
+ 
+   (if (starts-with? line [/connect]) (return (irc-handle-connect line)))
+   (if (starts-with? line [/user])    (return (irc-handle-user    line)))
+   (if (starts-with? line [/nick])    (return (irc-handle-nick    line)))
+ 
+   (if (== irc-socket nil) (return (irc-create-result 'ERROR [You must connect first])))
+ 
+   (if (starts-with? line [/join])    (return (irc-handle-join    line)))
+   (if (starts-with? line [/msg])     (return (irc-handle-msg     line)))
+   (if (starts-with? line [/part])    (return (irc-leave-current-channel)))
+ 
+   (return (irc-create-result 'ERROR (string-format [Unknown command "{}"] line))))
+   [handle command, for example /join] '(line [line of text starting with /]) howto-no)
+ 
+ (define irc-handle-message as (fn (line)
+   (if (== nil irc-current-channel)
+     (return (irc-create-result 'ERROR [You must enter a channel first])))
+   (define message as (string-format
+     [PRIVMSG {} :{}|newline|]
+     irc-current-channel
+     line)
+   [message])
+   (socket-send irc-socket message)
+   (return (irc-create-result
+     'USER
+     (string-format [<---------------You---------------> {}] line))))
+   [handle a line starting without /] '(line [line of text starting with /]) howto-no)
+ 
+ (define irc-handle-input-line as (fn (input)
+   (if (== {D+0} (len input)) (return (irc-create-result 'ERROR [NO INPUT])))
+   (define first-char as (cp@ input {D+0}) [first character of user input])
+   (if (== |/| first-char)
+     (return (irc-handle-command input))
+     (return (irc-handle-message input))))
+   [get user input and handle it] '(input [user input line]) howto-no)
+ 
+ (define irc-handle-ping as (fn (message)
+   (= message {D+1} |O|)
+   (socket-send irc-socket message)
+   (irc-create-result 'INFO [PING]))
+   [handle PING message from the IRC server] '(message [message]) howto-no)
+ 
+ (define irc-extract-only-message-from-input as (fn (message)
+   (define result as [] [result])
+   (define numsyms as {D+0} [number of syms])
+   (for i from {D+0} to (- (len message) {D+1})
+     (define c as (cp@ message i) [c])
+     (if (>= numsyms {D+2})
+       (+= result c)
+       (if (== c |:|) (++ numsyms))))
+   (if (>= numsyms {D+2}) result message))
+   [get only the actual message] '(message [message]) howto-no)
+ 
+ (define irc-read-socket as (fn ()
+   (if (== nil irc-socket) (return (irc-create-result 'ERROR [Not connected])))
+   (define input-from-socket as (socket-receive irc-socket false) [possible input from socket])
+   (if (== nil input-from-socket) (return (irc-create-result 'VERBOSE [No input])))
+   (define first-char as (cp@ input-from-socket {D+0}) [first character of user input])
+   (if (== |P| first-char)
+     (return (irc-handle-ping input-from-socket)))
+   (define tokens as (split input-from-socket | |) [line split by space])
+   (define command as [] [command])
+   (if (>= (len tokens) {D+2}) (= command (ln@ tokens {D+1})))
+   (if (== command [PRIVMSG])
+     (do
+       (define receiver as [] [channel or user])
+       (define sender   as [] [message is from])
+       (if (>= (len tokens) {D+1}) (= sender (substr (ln@ tokens {D+0}) {D+1})))
+       (if (>= (len tokens) {D+3}) (= receiver (ln@ tokens {D+2})))
+       (define msgToShow as (string-format
+         [<{}> {}]
+         sender
+         (irc-extract-only-message-from-input input-from-socket))
+        [msgToShow])
+       (return (irc-create-result
+         (if (== receiver irc-current-channel) 'MESSAGE 'PRIVATE-MESSAGE)
+         msgToShow))))
+     (return (irc-create-result 'OTHER input-from-socket)))
+   [read input from socket, if available] '() howto-no)

A release/examples/irc-client-ncurses.dern => release/examples/irc-client-ncurses.dern +118 -0
@@ 0,0 1,118 @@
+ (require 'dern_ncurses)
+ (require 'dern_irc)
+ 
+ (define win as (ncurses-initscr) [win])
+ (ncurses-set-keypad win true)
+ (ncurses-set-cbreak true)
+ (ncurses-set-nl true)
+ (ncurses-halfdelay {D+1})
+ (ncurses-set-echo false)
+ 
+ (if (ncurses-has-colors) (ncurses-start-color))
+ 
+ (ncurses-init-pair {D+1} 'RED    'BLACK)
+ (ncurses-init-pair {D+2} 'YELLOW 'BLUE)
+ (ncurses-init-pair {D+3} 'GREEN  'BLACK)
+ (ncurses-init-pair {D+4} 'CYAN   'BLACK)
+ 
+ (define num-rows-and-cols as (ncurses-getmaxyx win)           [number of rows and cols on screen] )
+ (define maxlines          as (- (cp@ num-rows-and-cols {D+0}) {D+4})  [maxlines] )
+ (define lines             as (queue-with-max-length maxlines) [messages] )
+ (define irc-client-ncurses-text-value as []                   [stores line of user input])
+ 
+ (= irc-line-length (- (cp@ num-rows-and-cols {D+1}) {D+1}))
+ 
+ (define row as {D+1} [current row])
+ 
+ (define irc-client-ncurses-print as (fn (text x color)
+   (if (ncurses-has-colors) (ncurses-attron color))
+   (ncurses-print win row x text)
+   (++ row)
+   (if (ncurses-has-colors) (ncurses-attroff color)))
+   [helper to print using ncurses] '(text [text] x [x] color [color]) howto-no)
+ 
+ (define irc-client-ncurses-render-message-lines as (fn (lines color)
+   (for line in lines
+     (define line2 as [] [test])
+     (for i from {D+0} to (- (len line) {D+1})
+       (define c as (cp@ line i) [c])
+       (if (or (< (to-integer c) {D+31}) (> (to-integer c) {D+126})) (= c |x|))
+       (+= line2 c))
+     (irc-client-ncurses-print line2 {D+1} color)))
+   [render body of one message] '(lines [lines of the message] color [color]) howto-no)
+ 
+ (define irc-client-ncurses-get-message-color as (fn (type)
+   (if (== type 'PRIVATE-MESSAGE) (return {D+2}))
+   (if (== type 'OTHER)           (return {D+1}))
+   (if (== type 'MESSAGE)         (return {D+3}))
+   (return {D+4}))
+   [get color for type and count of message] '(type [type of message]) howto-no)
+ 
+ (define irc-client-ncurses-send as (fn ()
+   (+= lines (irc-handle-input-line irc-client-ncurses-text-value))
+   (= irc-client-ncurses-text-value []))
+   [send current input] '() howto-no)
+ 
+ (define read-and-handle-input as (fn ()
+   (define char as (ncurses-getch) [octet of input from the user])
+ 
+   (if (== 'ERR char) (return))
+ 
+   (if (== 'KEY_ENTER char)
+     (do
+       (irc-client-ncurses-send)
+       (return)))
+ 
+   (if (and (or (== 'KEY_BACKSPACE char) (== 'KEY_DC char)) (> (len irc-client-ncurses-text-value) {D+0}))
+     (do
+       (-- irc-client-ncurses-text-value)
+       (return)))
+ 
+   (if (character? char)
+     (do
+       (+= irc-client-ncurses-text-value char)
+       (return))))
+     [read input and handle it if needed] '() howto-no)
+ 
+ (define irc-client-ncurses-render as (fn ()
+   (for line in lines
+     (irc-client-ncurses-render-message-lines
+       (ln@ line {D+2})
+       (irc-client-ncurses-get-message-color (ln@ line {D+0}))))
+ 
+   ; Hint label
+   (define statusbar as [] [statusbar])
+   (for i from (len irc-current-hint) to (- (cp@ num-rows-and-cols {D+1}) {D+2}) (+= statusbar |-|))
+   (+= statusbar irc-current-hint)
+   (irc-client-ncurses-print statusbar {D+1} {D+1})
+ 
+   ; Text input
+   (irc-client-ncurses-print irc-client-ncurses-text-value {D+1} {D+2})
+   (read-and-handle-input))
+   [render] '() howto-no)
+ 
+ (define irc-client-ncurses-read-and-handle-socket as (fn ()
+   (define line as (irc-read-socket) [line])
+   (define type as (cp@ line {D+0}) [type])
+   (define text as (cp@ line {D+1}) [type])
+   (if (not (irc-info-type? type))
+       (if (!= type 'VERBOSE)
+         (+= lines line))))
+   [read and handle socket] '() howto-no)
+ 
+ (define irc-client-ncurses-counter as {D+0} [delay counter])
+ 
+ (for i from {D+0} to maxlines
+   (+= lines (irc-create-result 'MESSAGE [~])))
+ 
+ (while irc-running
+   (if (<= irc-client-ncurses-counter {D+0})
+     (do
+       (= irc-client-ncurses-counter {D+30})
+       (irc-client-ncurses-read-and-handle-socket))
+     (-- irc-client-ncurses-counter))
+   (ncurses-clear win)
+   (= row {D+0})
+   (irc-client-ncurses-render))
+ 
+ (ncurses-endwin)

A release/examples/irc-client-nuklear.dern => release/examples/irc-client-nuklear.dern +151 -0
@@ 0,0 1,151 @@
+ (require 'dern_sdl2)
+ (require 'dern_nuklear)
+ (require 'dern_irc)
+ 
+ (= irc-line-length {D+107})
+ 
+ (sdl2-Init 'VIDEO)
+ 
+ (define irc-client-nuklear-window-width  as {D+800} [window width])
+ (define irc-client-nuklear-window-height as {D+600} [window width])
+ (define irc-client-nuklear-window-zoom   as {D+0.1} [window zoom factor])
+ 
+ (define maxlines          as {D+17} [TODO])
+ (define lines             as (queue-with-max-length maxlines) [messages] )
+ 
+ (define irc-client-nuklear-window as
+   (sdl2-CreateWindow
+     [Nuklear example]
+     'CENTERED
+     'CENTERED
+     irc-client-nuklear-window-width
+     irc-client-nuklear-window-height)
+   [window])
+ 
+ (define irc-client-nuklear-nuklear-ctx as (nuklear-sdl-init irc-client-nuklear-window) [nuklear context])
+ 
+ (sdl2-glClearColor {D+0} {D+0} {D+0} {D+1})
+ (sdl2-gl-ortho-line-smooth-enable)
+ 
+ (define irc-client-nuklear-enter-ortho as (fn (zoom)
+   (define width  as (/ (* {D+4} irc-client-nuklear-window-width)  (* {D+2} zoom)) [target width])
+   (define height as (/ (* {D+4} irc-client-nuklear-window-height) (* {D+2} zoom)) [target width])
+   (sdl2-gl-ortho-enter (- width) width (- height) height))
+   [enter ortho] '(zoom [zoom factor]) howto-no)
+ 
+ (irc-client-nuklear-enter-ortho irc-client-nuklear-window-zoom)
+ 
+ (define irc-client-nuklear-running               as true [is running or not])
+ (define irc-client-nuklear-target-FPS            as {D+50} [target FPS])
+ (define irc-client-nuklear-target-frame-duration as (/ {D+1.0} irc-client-nuklear-target-FPS) [time for a frame to last])
+ (define irc-client-nuklear-text-value            as []       [text input])
+ 
+ (define irc-client-nuklear-render-message-lines as (fn (lines color)
+   (for line in lines
+     (nuklear-layout-row-static irc-client-nuklear-nuklear-ctx {D+22} {D+750} {D+1})
+     (nuklear-label
+       irc-client-nuklear-nuklear-ctx
+       line
+       'LEFT
+       color)))
+   [render body of one message] '(lines [lines of the message] color [color]) howto-no)
+ 
+ (define irc-client-nuklear-get-message-color as (fn (type)
+   (if (== type 'PRIVATE-MESSAGE) (return (nuklear-rgba {X+FF} {X+FF} {X+0}  {X+FF})))
+   (if (== type 'OTHER)           (return (nuklear-rgba {X+FF} {X+4F} {X+0}  {X+FF})))
+   (if (== type 'MESSAGE)         (return (nuklear-rgba {X+0} {X+FF} {X+0}   {X+FF})))
+   (return (nuklear-rgba {X+AA} {X+AA} {X+AA} {X+FF})))
+   [get color for type and count of message] '(type [type of message]) howto-no)
+ 
+ (define irc-client-nuklear-send as (fn ()
+   (+= lines (irc-handle-input-line irc-client-nuklear-text-value))
+   (= irc-client-nuklear-text-value []))
+   [send current input] '() howto-no)
+ 
+ (define irc-client-nuklear-render as (fn ()
+   (if (nuklear-begin irc-client-nuklear-nuklear-ctx [] {D+10} {D+10} {D+780} {D+580})
+     (do
+       ; Text Labels
+       (for line in lines
+         (irc-client-nuklear-render-message-lines
+           (ln@ line {D+2})
+           (irc-client-nuklear-get-message-color (ln@ line {D+0}))))
+ 
+       ; Text Button
+       (nuklear-layout-row-static irc-client-nuklear-nuklear-ctx {D+30} {D+100} {D+1})
+       (if (nuklear-button-label
+             irc-client-nuklear-nuklear-ctx
+             [Send <enter>])
+           (irc-client-nuklear-send))
+ 
+       ; Hint label
+       (nuklear-layout-row-static irc-client-nuklear-nuklear-ctx {D+22} {D+750} {D+1})
+       (nuklear-label
+         irc-client-nuklear-nuklear-ctx
+         irc-current-hint
+         'RIGHT
+         (nuklear-rgba {X+FF} {X+0} {X+0} {X+FF}))
+ 
+       ; Focus on the next text input widget
+       (nuklear-edit-focus irc-client-nuklear-nuklear-ctx true)
+ 
+       ; Text input (simple)
+       (nuklear-layout-row-static irc-client-nuklear-nuklear-ctx {D+30} {D+750} {D+1})
+       (if (== [ACTIVE COMMITED ] (nuklear-edit-string
+         irc-client-nuklear-nuklear-ctx
+         'SIMPLE
+         irc-client-nuklear-text-value
+         {D+256}
+         'DEFAULT))
+         (irc-client-nuklear-send))))
+   (nuklear-end irc-client-nuklear-nuklear-ctx)
+   (nuklear-sdl-render)
+   (sdl2-GL-SwapWindow irc-client-nuklear-window))
+   [render] '() howto-no)
+ 
+ (define irc-client-nuklear-update as (fn (dt)
+   (nuklear-input-begin irc-client-nuklear-nuklear-ctx)
+   (define event as (sdl2-PollEvent) [event from sdl2])
+   (if (!= event nil)
+     (nuklear-sdl-handle-event irc-client-nuklear-nuklear-ctx (ln@ event (- (len event) {D+1}))))
+   (nuklear-input-end irc-client-nuklear-nuklear-ctx)
+   (define type  as nil [type of event])
+   (if (and (!= event nil) (> (len event) {D+0})) (= type (ln@ event {D+0})))
+   (if (== type 'QUIT) (= irc-client-nuklear-running false)))
+   [update] '(dt [dt]) howto-no)
+ 
+ (define irc-client-nuklear-read-and-handle-socket as (fn ()
+   (define line as (irc-read-socket) [line])
+   (define type as (cp@ line {D+0}) [type])
+   (define text as (cp@ line {D+1}) [type])
+   (if (not (irc-info-type? type))
+       (if (!= type 'VERBOSE)
+         (+= lines line))))
+   [read and handle socket] '() howto-no)
+ 
+ (define irc-client-nuklear-counter as {D+0} [delay counter])
+ 
+ (for i from {D+0} to maxlines
+   (+= lines (irc-create-result 'MESSAGE [~])))
+ 
+ (while (and irc-client-nuklear-running irc-running)
+   (sdl2-TimerUpdate)
+   (define dt as (sdl2-TimerGetSeconds) [dt])
+   (sdl2-TimerReset)
+   (if (<= irc-client-nuklear-counter {D+0})
+     (do
+       (= irc-client-nuklear-counter {D+90})
+       (irc-client-nuklear-read-and-handle-socket))
+     (-- irc-client-nuklear-counter))
+   (irc-client-nuklear-render)
+   (irc-client-nuklear-update dt)
+   (sdl2-TimerUpdate)
+   (define current-frame-duration as (sdl2-TimerGetSeconds) [current frame duration])
+   (if (> irc-client-nuklear-target-frame-duration current-frame-duration) (do
+     (define delay-in-seconds as (- irc-client-nuklear-target-frame-duration current-frame-duration) [time to sleep])
+     (if (> delay-in-seconds {D+0})
+         (sdl2-Delay delay-in-seconds))))
+   (= dt (sdl2-TimerGetSeconds)))
+ 
+ (sdl2-Quit)
+ 

D release/examples/irc-client.dern => release/examples/irc-client.dern +0 -404
@@ 1,404 0,0 @@-(require 'dern_ncurses)
- (require 'dern_socket)
- 
- (define win as (ncurses-initscr) [win])
- (ncurses-set-keypad win true)
- (ncurses-set-cbreak true)
- (ncurses-set-nl true)
- (ncurses-halfdelay {D+1})
- (ncurses-set-echo false)
- 
- (define running           as true                             [should this program be running])
- (define current-channel   as nil                              [currently joined channel])
- (define current-username  as []                               [username])
- (define current-realname  as []                               [realname])
- (define hint              as []                               [hint])
- (define rebuild-statusbar as true                             [should statusbar be (re)build])
- (define statusbar         as []                               [shows username, realname and hint])
- (define socket            as nil                              [socket])
- (define num-rows-and-cols as (ncurses-getmaxyx win)           [number of rows and cols on screen] )
- (define maxlines          as (- (cp@ num-rows-and-cols {D+0}) {D+3})  [maxlines] )
- (define lines             as (queue-with-max-length maxlines) [messages] )
- (define title             as []                               [title])
- (define debug             as false                            [is debug mode on])
- (define user-input-buffer as []                               [stores line of user input])
- (define redraw-required   as true                             [is (re)draw required or not])
- 
- (define PAIR-BORDER          as {D+1} [PAIR-BORDER])
- (define PAIR-PROMPT          as {D+2} [PAIR-PROMPT])
- (define PAIR-INPUT           as {D+3} [PAIR-INPUT])
- (define PAIR-OTHER           as {D+4} [PAIR-OTHER])
- (define PAIR-DEBUG           as {D+5} [PAIR-DEBUG])
- (define PAIR-USER            as {D+6} [PAIR-USER])
- (define PAIR-MESSAGE         as {D+7} [PAIR-MESSAGE])
- (define PAIR-PRIVATE-MESSAGE as {D+8} [PAIR-PRIVATE-MESSAGE])
- 
- (if (ncurses-has-colors)
-     (do
-       (ncurses-start-color)
-       (ncurses-init-pair PAIR-BORDER          'WHITE  'YELLOW)
-       (ncurses-init-pair PAIR-PROMPT          'YELLOW 'BLACK)
-       (ncurses-init-pair PAIR-INPUT           'WHITE  'BLACK)
-       (ncurses-init-pair PAIR-OTHER           'RED    'BLACK)
-       (ncurses-init-pair PAIR-DEBUG           'BLUE   'BLACK)
-       (ncurses-init-pair PAIR-USER            'WHITE  'BLACK)
-       (ncurses-init-pair PAIR-MESSAGE         'GREEN  'BLACK)
-       (ncurses-init-pair PAIR-PRIVATE-MESSAGE 'YELLOW 'BLACK)
-       ))
- 
- (define set-current-channel as (fn (channel)
-     (= current-channel channel)
-     (if (== current-channel nil) (set-title [-]) (set-title current-channel)))
-   [set current channel] '(channel [channel]) howto-no)
- 
- (define leave-current-channel as (fn ()
-     (define message as (string-format [PART {}|newline|] current-channel) [message])
-     (socket-send socket message)
-     (set-current-channel nil))
-   [leave the current channel] '() howto-no)
- 
- (define substr as (fn (str startIndex)
-     (define result as [] [result])
- 
-     (if (>= startIndex (len str)) (return result))
- 
-     (for i from startIndex to (- (len str) {D+1})
-          (+= result (cp@ str i)))
-     result)
-   [substr] '(str [str] startIndex [startIndex]) howto-no)
- 
- (define handle-nick as (fn (line)
-     (= current-username (substr line {D+6}))
-     (send-user-data))
-   [handle /nick] '(line [line of text starting with /nick]) howto-no)
- 
- (define handle-join as (fn (line)
-     (if (== current-username [])
-       (do (set-hint [Please set a nick first by using /nick or /user]) (return)))
- 
-     (define channel as (substr line {D+6}) [channel])
- 
-     (define message as (string-format [JOIN {}|newline|] channel) [message])
-     (socket-send socket message)
- 
-     (set-current-channel channel))
-   [handle /join] '(line [line of text starting with /join]) howto-no)
- 
- (define handle-msg as (fn (line)
-     (define restOfLine as (substr line {D+5}) [line without /msg])
- 
-     (define tokens as (split restOfLine | |) [line split by space])
- 
-     (if (< (len tokens) {D+2}) (do (set-hint [/msg expect target name and message]) (return)))
- 
-     (define target as (ln@ tokens {D+0}) [target] )
-     (define msgtxt as [] [msgtxt])
- 
-     (for i from {D+1} to (- (len tokens) {D+1})
-          (+= msgtxt (ln@ tokens i))
-          (if (< i (- (len tokens) {D+1})) (+= msgtxt | |)))
- 
-     (define message as (string-format [PRIVMSG {} :{}|newline|] target msgtxt) [message])
-     (socket-send socket message)
-     (push-text-to-lines 'PRIVATE-MESSAGE (string-format [<---------------You---------------> {}] msgtxt)))
-   [handle /msg] '(line [line of text starting with /msg]) howto-no)
- 
- (define set-hint as (fn (h)
-     (= hint h)
-     (= rebuild-statusbar true))
-   [Set the current hint] '(h [the new hint]) howto-no)
- 
- (define build-statusbar as (fn ()
-     (= redraw-required true)
-     (= rebuild-statusbar false)
-     (= statusbar (string-format [--/{}/{}/{}/--] current-username current-realname hint))
-     (for i from (len statusbar) to (- (cp@ num-rows-and-cols {D+1}) {D+1}) (+= statusbar |-|)))
-   [Build string for the status bar from the username, realname and hint] '() howto-no)
- 
- (define handle-connect as (fn (line)
-     (if (== current-username [])
-       (do (set-hint [Please set a nick first by using /nick or nick and a real name using /user]) (return)))
- 
-     (define restOfLine as (substr line {D+9})        [line without /connect])
-     (define tokens     as (split restOfLine | |)     [line split by space] )
-     (define address    as [localhost]                [address])
-     (define portnum    as {D+6667}                   [portnum])
- 
-     (if (>= (len tokens) {D+1}) (= address (ln@ tokens {D+0})))
-     (if (>= (len tokens) {D+2}) (= portnum (ln@ tokens {D+1})))
- 
-     (= portnum (to-integer portnum))
- 
-     ; Sockets cannot be copied with '='
-     (define socket as (socket-new-ipv4-stream-socket address portnum) [socket] in (env-global))
- 
-     (if (string? socket)
-       (do
-         (set-hint socket)
-         (= socket nil)
-         (return)))
- 
-     (read-socket)
-     (send-user-data)
-     (read-socket)
- 
-     (set-hint (string-format [connected to {} at port {}] address portnum)))
-   [handle /connect] '(line [line of text starting with /connect]) howto-no)
- 
- (define send-user-data as (fn ()
-     (if (== socket nil) (return))
- 
-     (socket-send socket (string-format [NICK {}|newline|] current-username))
-     (socket-send socket (string-format [USER {} 8 * :{}|newline|] current-username current-realname)))
-   [send nick and possible real name to server] '() howto-no)
- 
- (define handle-user as (fn (line)
-     (define restOfLine as (substr line {D+6}) [line without /user])
- 
-     (define tokens as (split restOfLine | |) [line split by space])
- 
-     (= current-username (cp@ tokens {D+0}))
-     (= current-realname [])
- 
-     (= rebuild-statusbar true)
- 
-     (if (> (len tokens) {D+1})
-         (for i from {D+1} to (- (len tokens) {D+1})
-              (+= current-realname (cp@ tokens i))
-              (if (< i (- (len tokens) {D+1})) (+= current-realname | |))))
- 
-     (send-user-data))
-   [handle /user] '(line [line of text starting with /user]) howto-no)
- 
- (define handle-command as (fn (line)
-     (if (starts-with? line [/quit])    (do (= running false)       (return)))
-     (if (starts-with? line [/debug])   (do (= debug (not debug))   (return)))
-     (if (starts-with? line [/connect]) (do (handle-connect line)   (return)))
- 
-     (if (starts-with? line [/user])    (do (handle-user    line)   (return)))
-     (if (starts-with? line [/nick])    (do (handle-nick    line)   (return)))
- 
-     (if (== socket nil) (do (set-hint [You must connect first])    (return)))
- 
-     (if (starts-with? line [/join])    (do (handle-join    line)   (return)))
-     (if (starts-with? line [/msg])     (do (handle-msg     line)   (return)))
-     (if (starts-with? line [/part])    (do (leave-current-channel) (return)))
- 
-     (set-hint (string-format [Unknown command "{}"] line)))
-   [handle command, for example /join] '(line [line of text starting with /]) howto-no)
- 
- (define handle-message as (fn (line)
-     (if (== nil current-channel)
-       (do
-         (set-hint [You must enter a channel first])
-         (return)))
- 
-     (define message as (string-format [PRIVMSG {} :{}|newline|] current-channel line) [message])
-     (socket-send socket message)
-     (push-text-to-lines 'USER (string-format [<---------------You---------------> {}] line)))
-   [handle a line starting without /] '(line [line of text starting with /]) howto-no)
- 
- (define show-title as (fn (t)
-     (ncurses-attron 'BOLD)
-     (if (ncurses-has-colors) (ncurses-attron PAIR-BORDER))
-     (ncurses-print win {D+0} {D+0} t)
-     (if (ncurses-has-colors) (ncurses-attroff PAIR-BORDER))
-     (ncurses-attroff 'BOLD))
-   [show title bar] '(t [title message]) howto-no)
- 
- (define show-statusbar as (fn ()
-     (ncurses-attron 'BOLD)
-     (if (ncurses-has-colors) (ncurses-attron PAIR-BORDER))
-     (ncurses-print win (- (cp@ num-rows-and-cols {D+0}) {D+2}) {D+0} statusbar)
-     (if (ncurses-has-colors) (ncurses-attroff PAIR-BORDER))
-     (ncurses-attroff 'BOLD))
-   [draw statusbar into display] '() howto-no)
- 
- (define set-title as (fn (m)
-     (= title (string-format [--{}] m))
-     (for i from (len title) to (- (cp@ num-rows-and-cols {D+1}) {D+1}) (+= title |-|)))
-   [show title message] '(m [title message]) howto-no)
- 
- (define show-input-line as (fn ()
-     (ncurses-attron 'BOLD)
-     (ncurses-attron PAIR-PROMPT)
-     (ncurses-print win (- (cp@ num-rows-and-cols {D+0}) {D+1}) {D+0} (string-format [>{}] user-input-buffer))
-     (ncurses-attroff PAIR-PROMPT)
-     (ncurses-attroff 'BOLD)
-     (ncurses-attroff PAIR-INPUT)
-     (ncurses-move win (- (cp@ num-rows-and-cols {D+0}) {D+1}) (+ (len user-input-buffer) {D+1})))
-     [show user input] '() howto-no)
- 
- (define read-and-handle-input as (fn ()
-     (define char as (ncurses-getch) [octet of input from the user])
- 
-     (if (== 'ERR char) (return))
- 
-     (= redraw-required true)
- 
-     (if (== 'KEY_ENTER char)
-           (do
-             (handle-input-line)
-             (= user-input-buffer [])
-             (show-input-line)
-             (return)))
- 
-     (if (and (or (== 'KEY_BACKSPACE char) (== 'KEY_DC char)) (> (len user-input-buffer) {D+0}))
-           (do
-             (-- user-input-buffer)
-             (show-input-line)
-             (return)))
- 
-     (if (character? char)
-         (do
-           (+= user-input-buffer char)
-           (show-input-line)
-           (return))))
-     [read input and handle it if needed] '() howto-no)
- 
- (define handle-input-line as (fn ()
-     (if (== {D+0} (len user-input-buffer)) (return))
- 
-     (define first-char as (cp@ user-input-buffer {D+0}) [first character of user input])
- 
-     (if (== |/| first-char)
-       (handle-command user-input-buffer)
-       (handle-message user-input-buffer)))
-   [get user input and handle it] '() howto-no)
- 
- (define handle-ping as (fn (message)
-     (= message {D+1} |O|)
-     ;(+= lines message) ; TODO remove
-     (socket-send socket message))
-   [handle PING message from the IRC server] '(message [message]) howto-no)
- 
- (define push-text-to-lines as (fn (type text)
-     (define line as [] [tmp line])
- 
-     (if (== {D+0} (len text)) (return))
- 
-     (for i from {D+0} to (- (len text) {D+1})
-          (define c as (cp@ text i) [c])
-          (if (== c |newline|)
-             (do (+= lines (vector type line))
-                 (=  line []))
-             (+= line c))
-          (if (>= (len line) (cp@ num-rows-and-cols {D+1}))
-            (do
-              (+= lines (vector type line))
-              (= line []))))
- 
-     (if (> (len line) {D+0})
-         (do (+= lines (vector type line)))))
-   [push text to lines] '(type [type] text [text]) howto-no)
- 
- (define extract-only-message-from-input as (fn (message)
- 
-     (define result as [] [result])
-     (define numsyms as {D+0} [number of syms])
- 
-     (for i from {D+0} to (- (len message) {D+1})
-         (define c as (cp@ message i) [c])
-         (if (>= numsyms {D+2})
-           (+= result c)
-           (if (== c |:|) (++ numsyms))))
- 
-     (if (>= numsyms {D+2}) result message))
-   [get only the actual message] '(message [message]) howto-no)
- 
- (define read-socket as (fn ()
-     (if (== socket nil) (return))
- 
-     (define input-from-socket as (socket-receive socket false) [possible input from socket] )
- 
-     (if (== nil input-from-socket) (return))
- 
-     (= redraw-required true)
- 
-     (if debug (push-text-to-lines 'DEBUG input-from-socket))
- 
-     (define first-char as (cp@ input-from-socket {D+0}) [first character of user input])
- 
-     (if (== |P| first-char)
-         (return (handle-ping input-from-socket)))
- 
-     (define tokens as (split input-from-socket | |) [line split by space])
- 
-     (define command as [] [command])
- 
-     (if (>= (len tokens) {D+2}) (= command (ln@ tokens {D+1})))
- 
-     (if (== command [PRIVMSG])
-       (do
-         (define receiver as [] [channel or user])
-         (define sender   as [] [message is from])
- 
-         (if (>= (len tokens) {D+1}) (= sender (substr (ln@ tokens {D+0}) {D+1})))
-         (if (>= (len tokens) {D+3}) (= receiver (ln@ tokens {D+2})))
- 
-         (if (> (len sender) {D+32})
-           (do
-             (for i from {D+0} to {D+3} (-- sender))
-             (+= sender [...])))
- 
-         ; TODO XXX fix crash on (++ sender | |)
-         (if (< (len sender) {D+32})
-             (for i from (len sender) to {D+32} (+= sender |-|)))
- 
-         (define msgToShow as
-           (string-format [<{}> {}] sender (extract-only-message-from-input input-from-socket)) [msgToShow])
- 
-         (push-text-to-lines (if (== receiver current-channel) 'MESSAGE 'PRIVATE-MESSAGE) msgToShow)
- 
-         (return)))
- 
-     (push-text-to-lines 'OTHER input-from-socket))
-   [read input from socket, if available] '() howto-no)
- 
- (define print-lines as (fn ()
-     (show-title title)
-     (show-statusbar)
- 
-     (define row as {D+1} [current row])
-     (for line in lines
-          (define type as (cp@ line {D+0}) [type])
- 
-          (if (== type 'OTHER)           (ncurses-attron PAIR-OTHER))
-          (if (== type 'DEBUG)           (ncurses-attron PAIR-DEBUG))
-          (if (== type 'USER)            (ncurses-attron PAIR-USER))
-          (if (== type 'MESSAGE)         (ncurses-attron PAIR-MESSAGE))
- 
-          (if (== type 'PRIVATE-MESSAGE) (do (ncurses-attron PAIR-PRIVATE-MESSAGE)
-                                             (ncurses-attron 'BOLD)))
- 
-          (ncurses-print win row {D+1} (ln@ line {D+1}))
- 
-          (if (== type 'OTHER)           (ncurses-attroff PAIR-OTHER))
-          (if (== type 'DEBUG)           (ncurses-attroff PAIR-DEBUG))
-          (if (== type 'USER)            (ncurses-attroff PAIR-USER))
-          (if (== type 'MESSAGE)         (ncurses-attroff PAIR-MESSAGE))
- 
-          (if (== type 'PRIVATE-MESSAGE) (do (ncurses-attroff PAIR-PRIVATE-MESSAGE)
-                                             (ncurses-attroff 'BOLD)))
- 
-          (++ row))
- 
-          (ncurses-refresh)
-     )
-   [print messages] '() howto-no)
- 
- (set-title [-])
- 
- (while running
-     (read-socket)
-     (read-and-handle-input)
-     (if rebuild-statusbar (build-statusbar))
-     (if redraw-required
-       (do
-         (ncurses-clear win)
-         (print-lines)
-         (show-input-line)
-         (= redraw-required false))))
- 
- (ncurses-endwin)
- 

M release/how-to-build/OpenBSD.sh => release/how-to-build/OpenBSD.sh +1 -1
@@ 64,7 64,7 @@   EXAMPLE_NAME="Dern socket plugin"
  EXAMPLE_ERROR_HINT="Install $CC compiler?"
- EXAMPLE_SUCCESS_RUN="LD_LIBRARY_PATH=. ./octaspire-dern-repl examples/irc-client.dern"
+ EXAMPLE_SUCCESS_RUN="LD_LIBRARY_PATH=. ./octaspire-dern-repl -I examples examples/irc-client-ncurses.dern"
  echoAndRun "$CC" -O2 -std=c99 -Wall -Wextra -fPIC -shared -I . -o libdern_socket.so plugins/dern_socket.c
  
  

M release/octaspire-dern-amalgamated.c => release/octaspire-dern-amalgamated.c +141 -14
@@ 64,6 64,7 @@ #include <limits.h>
  #include <wchar.h>
  #include <locale.h>
+ #include <errno.h>
  
  #endif
  


@@ 26223,7 26224,7 @@ #define OCTASPIRE_DERN_CONFIG_H
  
  #define OCTASPIRE_DERN_CONFIG_VERSION_MAJOR "0"
- #define OCTASPIRE_DERN_CONFIG_VERSION_MINOR "472"
+ #define OCTASPIRE_DERN_CONFIG_VERSION_MINOR "473"
  #define OCTASPIRE_DERN_CONFIG_VERSION_PATCH "0"
  
  #define OCTASPIRE_DERN_CONFIG_VERSION_STR "Octaspire Dern version " \


@@ 27720,6 27721,7 @@ octaspire_dern_vm_custom_require_source_file_loader_t preLoaderForRequireSrc;
      bool debugModeOn;
      bool noDlClose;
+     octaspire_vector_t * includeDirectories;
  }
  octaspire_dern_vm_config_t;
  


@@ 37500,10 37502,23 @@ "Builtin 'io-file-open' expects string argument.");
      }
  
-     octaspire_dern_value_t * const result = octaspire_dern_vm_create_new_value_io_file(
+     octaspire_dern_value_t * result = octaspire_dern_vm_create_new_value_io_file(
          vm,
          octaspire_dern_value_as_string_get_c_string(firstArg));
  
+     octaspire_helpers_verify_not_null(result);
+     octaspire_helpers_verify_not_null(result->value.port);
+ 
+     if (!octaspire_dern_port_supports_output(result->value.port) ||
+         !octaspire_dern_port_supports_input(result->value.port))
+     {
+         result = octaspire_dern_vm_create_new_value_error_format(
+             vm,
+             "Builtin 'io-file-open' failed to open file '%s': %s.",
+             octaspire_dern_value_as_string_get_c_string(firstArg),
+             strerror(errno));
+     }
+ 
      octaspire_dern_vm_pop_value(vm, arguments);
      octaspire_helpers_verify_true(stackLength == octaspire_dern_vm_get_stack_length(vm));
      return result;


@@ 42909,10 42924,58 @@ }
      else
      {
-         input = octaspire_input_new_from_path(
-             octaspire_string_get_c_string(fileName),
-             octaspire_dern_vm_get_allocator(vm),
-             octaspire_dern_vm_get_stdio(vm));
+         octaspire_dern_vm_config_t const * const config =
+             octaspire_dern_vm_get_config_const(vm);
+ 
+         octaspire_helpers_verify_not_null(config);
+ 
+         if (config->includeDirectories)
+         {
+             for (size_t i = 0;
+                  i < octaspire_vector_get_length(config->includeDirectories);
+                  ++i)
+             {
+                 octaspire_string_t const * const str =
+                     octaspire_vector_get_element_at_const(config->includeDirectories, i);
+ 
+                 octaspire_helpers_verify_not_null(str);
+ 
+ #ifdef _WIN32
+                 char const * const pathSeparator = "\\";
+ #else
+                 char const * const pathSeparator = "/";
+ #endif
+ 
+                 octaspire_string_t * newPath = octaspire_string_new_format(
+                     octaspire_dern_vm_get_allocator(vm),
+                     "%s%s%s",
+                     octaspire_string_get_c_string(str),
+                     pathSeparator,
+                     octaspire_string_get_c_string(fileName));
+ 
+                 octaspire_helpers_verify_not_null(newPath);
+ 
+                 input = octaspire_input_new_from_path(
+                     octaspire_string_get_c_string(newPath),
+                     octaspire_dern_vm_get_allocator(vm),
+                     octaspire_dern_vm_get_stdio(vm));
+ 
+                 octaspire_string_release(newPath);
+                 newPath = 0;
+ 
+                 if (input)
+                 {
+                     break;
+                 }
+             }
+         }
+         else
+         {
+             input = octaspire_input_new_from_path(
+                 octaspire_string_get_c_string(fileName),
+                 octaspire_dern_vm_get_allocator(vm),
+                 octaspire_dern_vm_get_stdio(vm));
+         }
      }
  
      if (!input)


@@ 43674,6 43737,20 @@ }
  
          case OCTASPIRE_DERN_VALUE_TAG_NIL:
+         {
+             octaspire_helpers_verify_true(
+                 stackLength == octaspire_dern_vm_get_stack_length(vm));
+ 
+             if (numArgs > 2)
+             {
+                 return octaspire_dern_vm_create_new_value_error_from_c_string(
+                     vm,
+                     "Builtin 'cp@' expects exactly one argument when used with nil.");
+             }
+ 
+             return octaspire_dern_vm_create_new_value_nil(vm);
+         }
+ 
          case OCTASPIRE_DERN_VALUE_TAG_BOOLEAN:
          case OCTASPIRE_DERN_VALUE_TAG_INTEGER:
          case OCTASPIRE_DERN_VALUE_TAG_REAL:


@@ 49331,7 49408,8 @@ {
          .preLoaderForRequireSrc  = 0,
          .debugModeOn             = false,
-         .noDlClose               = false
+         .noDlClose               = false,
+         .includeDirectories      = 0
      };
  
      return result;


@@ 55151,6 55229,7 @@ "If any of -e string or [file] is used, REPL is not started unless -i is used.\n\n"
          "-c        --color-diagnostics : use colors on unix like systems\n"
          "-i        --interactive       : start REPL after any -e string or [file]s are evaluated\n"
+         "-I dir    --include dir       : Search this directory for source (.dern) libraries\n"
          "-e string --evaluate string   : evaluate a string without entering the REPL (see -i)\n"
          "-v        --version           : print version information and exit\n"
          "-h        --help              : print this help message and exit\n"


@@ 55164,19 55243,23 @@   
  // Globals for the REPL. ////////////////////////////
- static octaspire_vector_t      *stringsToBeEvaluated = 0;
- static octaspire_allocator_t      *allocatorBootOnly    = 0;
- static octaspire_string_t *line                 = 0;
- static octaspire_stdio_t                 *stdio                = 0;
- static octaspire_input_t                 *input                = 0;
- static octaspire_dern_vm_t               *vm                   = 0;
- static octaspire_allocator_t      *allocator            = 0;
+ static octaspire_vector_t    *stringsToBeEvaluated = 0;
+ static octaspire_vector_t    *includeDirectories   = 0;
+ static octaspire_allocator_t *allocatorBootOnly    = 0;
+ static octaspire_string_t    *line                 = 0;
+ static octaspire_stdio_t     *stdio                = 0;
+ static octaspire_input_t     *input                = 0;
+ static octaspire_dern_vm_t   *vm                   = 0;
+ static octaspire_allocator_t *allocator            = 0;
  
  static void octaspire_dern_repl_private_cleanup(void)
  {
      octaspire_vector_release(stringsToBeEvaluated);
      stringsToBeEvaluated = 0;
  
+     octaspire_vector_release(includeDirectories);
+     includeDirectories = 0;
+ 
      octaspire_allocator_release(allocatorBootOnly);
      allocatorBootOnly = 0;
  


@@ 55212,6 55295,7 @@ int  userFilesStartIdx       = -1;
      bool enterReplAlways         = false;
      bool evaluate                = false;
+     bool include                 = false;
  
      octaspire_dern_vm_config_t vmConfig = octaspire_dern_vm_config_default();
  


@@ 55260,6 55344,23 @@ exit(EXIT_FAILURE);
      }
  
+     includeDirectories = octaspire_vector_new(
+         sizeof(octaspire_string_t*),
+         true,
+         (octaspire_vector_element_callback_t)octaspire_string_release,
+         allocatorBootOnly);
+ 
+     if (!includeDirectories)
+     {
+         octaspire_dern_repl_print_message_c_str(
+             "Cannot create include directory vector",
+             OCTASPIRE_DERN_REPL_MESSAGE_FATAL,
+             useColors,
+             0);
+ 
+         exit(EXIT_FAILURE);
+     }
+ 
      if (argc > 1)
      {
          for (int i = 1; i < argc; ++i)


@@ 55285,6 55386,28 @@                   octaspire_vector_push_back_element(stringsToBeEvaluated, &tmp);
              }
+             else if (include)
+             {
+                 include = false;
+ 
+                 octaspire_string_t *tmp = octaspire_string_new(
+                     argv[i],
+                     allocatorBootOnly);
+ 
+                 if (!tmp)
+                 {
+                     octaspire_dern_repl_print_message_c_str(
+                         "Cannot create string for include path",
+                         OCTASPIRE_DERN_REPL_MESSAGE_FATAL,
+                         useColors,
+                         0);
+ 
+                     exit(EXIT_FAILURE);
+                 }
+ 
+                 octaspire_vector_push_back_element(includeDirectories, &tmp);
+                 vmConfig.includeDirectories = includeDirectories;
+             }
              else if (strcmp(argv[i], "-c") == 0 || strcmp(argv[i], "--color-diagnostics") == 0)
              {
                  useColors = true;


@@ 55293,6 55416,10 @@ {
                  enterReplAlways = true;
              }
+             else if (strcmp(argv[i], "-I") == 0 || strcmp(argv[i], "--include") == 0)
+             {
+                 include = true;
+             }
              else if (strcmp(argv[i], "-e") == 0 || strcmp(argv[i], "--evaluate") == 0)
              {
                  evaluate = true;

M release/plugins/dern_nuklear.c => release/plugins/dern_nuklear.c +274 -2
@@ 447,6 447,120 @@ return octaspire_dern_vm_create_new_value_boolean(vm, true);
  }
  
+ octaspire_dern_value_t *dern_nuklear_label_wrap(
+     octaspire_dern_vm_t * const vm,
+     octaspire_dern_value_t * const arguments,
+     octaspire_dern_value_t * const environment)
+ {
+     OCTASPIRE_HELPERS_UNUSED_PARAMETER(environment);
+ 
+     size_t const stackLength = octaspire_dern_vm_get_stack_length(vm);
+     char   const * const dernFuncName     = "nuklear-label";
+     char   const * const ctxName          = "ctx";
+     char   const * const nkColorName      = "nk_color";
+     size_t const         numExpectedArgs1 = 2;
+     size_t const         numExpectedArgs2 = 3;
+ 
+     size_t const numArgs =
+         octaspire_dern_value_as_vector_get_length(arguments);
+ 
+     if (numArgs != numExpectedArgs1 && numArgs != numExpectedArgs2)
+     {
+         octaspire_helpers_verify_true(
+             stackLength == octaspire_dern_vm_get_stack_length(vm));
+ 
+         return octaspire_dern_vm_create_new_value_error_format(
+             vm,
+             "Builtin '%s' expects %zu or %zu arguments. "
+             "%zu arguments were given.",
+             dernFuncName,
+             numExpectedArgs1,
+             numExpectedArgs2,
+             numArgs);
+     }
+ 
+     // ctx
+ 
+     octaspire_dern_c_data_or_unpushed_error_t cDataOrErrorCtx =
+         octaspire_dern_value_as_vector_get_element_at_as_c_data_or_unpushed_error(
+             arguments,
+             0,
+             dernFuncName,
+             ctxName,
+             DERN_NUKLEAR_PLUGIN_NAME);
+ 
+     if (cDataOrErrorCtx.unpushedError)
+     {
+         octaspire_helpers_verify_true(
+             stackLength == octaspire_dern_vm_get_stack_length(vm));
+ 
+         return cDataOrErrorCtx.unpushedError;
+     }
+ 
+     struct nk_context * const ctx = cDataOrErrorCtx.cData;
+ 
+     octaspire_helpers_verify_not_null(ctx);
+ 
+     octaspire_dern_value_t const * const secondArg =
+         octaspire_dern_value_as_vector_get_element_at_const(arguments, 1);
+ 
+     octaspire_helpers_verify_not_null(secondArg);
+ 
+     if (!octaspire_dern_value_is_text(secondArg))
+     {
+         octaspire_helpers_verify_true(
+             stackLength == octaspire_dern_vm_get_stack_length(vm));
+ 
+         return octaspire_dern_vm_create_new_value_error_format(
+             vm,
+             "Builtin '%s' expects text (string or symbol) as the second "
+             "argument. Type '%s' was given.",
+             dernFuncName,
+             octaspire_dern_value_helper_get_type_as_c_string(secondArg->typeTag));
+     }
+ 
+     char const * const text = octaspire_dern_value_as_text_get_c_string(secondArg);
+ 
+     if (numArgs == 2)
+     {
+         nk_label_wrap(ctx, text);
+     }
+     else
+     {
+         octaspire_dern_c_data_or_unpushed_error_t cDataOrErrorNkColor =
+             octaspire_dern_value_as_vector_get_element_at_as_c_data_or_unpushed_error(
+                 arguments,
+                 2,
+                 dernFuncName,
+                 nkColorName,
+                 DERN_NUKLEAR_PLUGIN_NAME);
+ 
+         if (cDataOrErrorNkColor.unpushedError)
+         {
+             octaspire_helpers_verify_true(
+                 stackLength == octaspire_dern_vm_get_stack_length(vm));
+ 
+             return cDataOrErrorNkColor.unpushedError;
+         }
+ 
+         dern_nuklear_allocation_context_t const * const context =
+             cDataOrErrorNkColor.cData;
+ 
+         octaspire_helpers_verify_not_null(context);
+ 
+         struct nk_color * const color = context->payload;
+ 
+         octaspire_helpers_verify_not_null(color);
+ 
+         nk_label_colored_wrap(ctx, text, *color);
+     }
+ 
+     octaspire_helpers_verify_true(
+         stackLength == octaspire_dern_vm_get_stack_length(vm));
+ 
+     return octaspire_dern_vm_create_new_value_boolean(vm, true);
+ }
+ 
  octaspire_dern_value_t *dern_nuklear_button_label(
      octaspire_dern_vm_t * const vm,
      octaspire_dern_value_t * const arguments,


@@ 1125,6 1239,12 @@ case 3: { editType = NK_EDIT_EDITOR; } break;
      }
  
+     // TODO This allows committing the changes in the text box
+     // by using <ENTER>, but maybe this should not be hard-coded;
+     // it might be better to allow it to be given as an argument
+     // to this function somehow.
+     editType |= NK_EDIT_SIG_ENTER;
+ 
      // str
  
      octaspire_dern_string_or_unpushed_error_t stringOrError =


@@ 1244,7 1364,7 @@       nk_flags const resultFlags = nk_edit_string(
          ctx,
-         NK_EDIT_SIMPLE,
+         editType,
          memory,
          &memused,
          len,


@@ 1304,6 1424,92 @@ return octaspire_dern_vm_create_new_value_string(vm, result);
  }
  
+ octaspire_dern_value_t *dern_nuklear_edit_focus(
+     octaspire_dern_vm_t * const vm,
+     octaspire_dern_value_t * const arguments,
+     octaspire_dern_value_t * const environment)
+ {
+     OCTASPIRE_HELPERS_UNUSED_PARAMETER(environment);
+ 
+     size_t const stackLength = octaspire_dern_vm_get_stack_length(vm);
+     char   const * const dernFuncName    = "nuklear-edit-focus";
+     char   const * const ctxName         = "ctx";
+     size_t const         numExpectedArgs = 2;
+ 
+     size_t const numArgs =
+         octaspire_dern_value_as_vector_get_length(arguments);
+ 
+     if (numArgs != numExpectedArgs)
+     {
+         octaspire_helpers_verify_true(
+             stackLength == octaspire_dern_vm_get_stack_length(vm));
+ 
+         return octaspire_dern_vm_create_new_value_error_format(
+             vm,
+             "Builtin '%s' expects %zu arguments. "
+             "%zu arguments were given.",
+             dernFuncName,
+             numExpectedArgs,
+             numArgs);
+     }
+ 
+     // ctx
+ 
+     octaspire_dern_c_data_or_unpushed_error_t cDataOrError =
+         octaspire_dern_value_as_vector_get_element_at_as_c_data_or_unpushed_error(
+             arguments,
+             0,
+             dernFuncName,
+             ctxName,
+             DERN_NUKLEAR_PLUGIN_NAME);
+ 
+     if (cDataOrError.unpushedError)
+     {
+         octaspire_helpers_verify_true(
+             stackLength == octaspire_dern_vm_get_stack_length(vm));
+ 
+         return cDataOrError.unpushedError;
+     }
+ 
+     struct nk_context * const ctx = cDataOrError.cData;
+ 
+     octaspire_helpers_verify_not_null(ctx);
+ 
+     // focus
+ 
+     octaspire_dern_boolean_or_unpushed_error_const_t boolOrError =
+         octaspire_dern_value_as_vector_get_element_at_as_boolean_or_unpushed_error_const(
+             arguments,
+             1,
+             dernFuncName);
+ 
+     if (boolOrError.unpushedError)
+     {
+         octaspire_helpers_verify_true(
+             stackLength == octaspire_dern_vm_get_stack_length(vm));
+ 
+         return boolOrError.unpushedError;
+     }
+ 
+     bool const focus = boolOrError.boolean;
+ 
+     // Apply the requested focusing.
+ 
+     if (focus)
+     {
+         nk_edit_focus(ctx, NK_EDIT_ALWAYS_INSERT_MODE);
+     }
+     else
+     {
+         nk_edit_unfocus(ctx);
+     }
+ 
+     octaspire_helpers_verify_true(
+         stackLength == octaspire_dern_vm_get_stack_length(vm));
+ 
+     return octaspire_dern_vm_create_new_value_boolean(vm, true);
+ }
+ 
  octaspire_dern_value_t *dern_nuklear_progress(
      octaspire_dern_vm_t * const vm,
      octaspire_dern_value_t * const arguments,


@@ 2341,7 2547,41 @@ "\ttrue or error if something went wrong.\n"
              "\n"
              "SEE ALSO\n"
-             "\tnuklear-begin\n",
+             "\tnuklear-begin\n"
+             "\tnuklear-label-wrap\n",
+             false,
+             targetEnv))
+     {
+         return false;
+     }
+ 
+     if (!octaspire_dern_vm_create_and_register_new_builtin(
+             vm,
+             "nuklear-label-wrap",
+             dern_nuklear_label_wrap,
+             3,
+             "NAME\n"
+             "\tnuklear-label-wrap\n"
+             "\n"
+             "SYNOPSIS\n"
+             "\t(require 'dern_nuklear)\n"
+             "\n"
+             "\t(nuklear-label-wrap ctx text <color>) -> true or error\n"
+             "\n"
+             "DESCRIPTION\n"
+             "\tDisplay a text label with wrapped text.\n"
+             "\n"
+             "ARGUMENTS\n"
+             "\tctx           nuklear context.\n"
+             "\ttext          text to be shown.\n"
+             "\tcolor         optional color.\n"
+             "\n"
+             "RETURN VALUE\n"
+             "\ttrue or error if something went wrong.\n"
+             "\n"
+             "SEE ALSO\n"
+             "\tnuklear-begin\n"
+             "\tnuklear-label\n",
              false,
              targetEnv))
      {


@@ 2593,6 2833,38 @@       if (!octaspire_dern_vm_create_and_register_new_builtin(
              vm,
+             "nuklear-edit-focus",
+             dern_nuklear_edit_focus,
+             2,
+             "NAME\n"
+             "\tnuklear-edit-focus\n"
+             "\n"
+             "SYNOPSIS\n"
+             "\t(require 'dern_nuklear)\n"
+             "\n"
+             "\t(nuklear-edit-focus ctx focus) -> true or error\n"
+             "\n"
+             "DESCRIPTION\n"
+             "\tSet focus of the next text entry programmatically.\n"
+             "\n"
+             "ARGUMENTS\n"
+             "\tctx           nuklear context.\n"
+             "\tfocus         boolean value for the focus."
+             "\n"
+             "RETURN VALUE\n"
+             "\ttrue or error if something went wrong.\n"
+             "\n"
+             "SEE ALSO\n"
+             "\tnuklear-begin\n"
+             "\tnuklear-edit-string\n",
+             false,
+             targetEnv))
+     {
+         return false;
+     }
+ 
+     if (!octaspire_dern_vm_create_and_register_new_builtin(
+             vm,
              "nuklear-layout-row-dynamic",
              dern_nuklear_layout_row_dynamic,
              3,