Przeglądaj źródła

ncd: improve how arguments are instantiated. Keep the incomplete arguments in NCDValMem's, but containing placholder values in place
of variables. When the arguments need to be instantiated, the entire NCDValMem is byte-copied, and the placeholders are replaced
in-place with real values. This removes the NCDInterpValue stuff, and is slightly faster.

ambrop7 13 lat temu
rodzic
commit
60249f255b

+ 2 - 0
blog_channels.txt

@@ -123,3 +123,5 @@ ncd_call2 4
 ncd_assert 4
 ncd_reboot 4
 ncd_explode 4
+NCDPlaceholderDb 4
+NCDVal 4

+ 3 - 0
examples/ncdval_test.c

@@ -1,6 +1,7 @@
 #include <stdio.h>
 
 #include <ncd/NCDVal.h>
+#include <base/BLog.h>
 #include <misc/debug.h>
 
 #define FORCE(cmd) if (!(cmd)) { fprintf(stderr, "failed\n"); exit(1); }
@@ -54,6 +55,8 @@ static void print_value (NCDValRef val, unsigned int indent)
 
 int main ()
 {
+    BLog_InitStdout();
+    
     // Some basic usage of values.
     
     NCDValMem mem;

+ 4 - 0
generated/blog_channel_NCDPlaceholderDb.h

@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDPlaceholderDb

+ 4 - 0
generated/blog_channel_NCDVal.h

@@ -0,0 +1,4 @@
+#ifdef BLOG_CURRENT_CHANNEL
+#undef BLOG_CURRENT_CHANNEL
+#endif
+#define BLOG_CURRENT_CHANNEL BLOG_CHANNEL_NCDVal

+ 3 - 1
generated/blog_channels_defines.h

@@ -123,4 +123,6 @@
 #define BLOG_CHANNEL_ncd_assert 122
 #define BLOG_CHANNEL_ncd_reboot 123
 #define BLOG_CHANNEL_ncd_explode 124
-#define BLOG_NUM_CHANNELS 125
+#define BLOG_CHANNEL_NCDPlaceholderDb 125
+#define BLOG_CHANNEL_NCDVal 126
+#define BLOG_NUM_CHANNELS 127

+ 2 - 0
generated/blog_channels_list.h

@@ -123,3 +123,5 @@
 {"ncd_assert", 4},
 {"ncd_reboot", 4},
 {"ncd_explode", 4},
+{"NCDPlaceholderDb", 4},
+{"NCDVal", 4},

+ 2 - 1
ncd/CMakeLists.txt

@@ -33,6 +33,7 @@ add_library(ncdvalue
 add_library(ncdval
     NCDVal.c
 )
+target_link_libraries(ncdval base)
 
 add_library(ncdvalcompat
     NCDValCompat.c
@@ -82,7 +83,7 @@ add_executable(badvpn-ncd
     NCDObject.c
     NCDInterpBlock.c
     NCDInterpProg.c
-    NCDInterpValue.c
+    NCDPlaceholderDb.c
     BEventLock.c
     modules/command_template.c
     modules/event_template.c

+ 115 - 10
ncd/NCDInterpBlock.c

@@ -68,8 +68,89 @@ static int compute_prealloc (NCDInterpBlock *o)
     return 1;
 }
 
-int NCDInterpBlock_Init (NCDInterpBlock *o, NCDBlock *block, NCDProcess *process)
+int convert_value_recurser (NCDPlaceholderDb *pdb, NCDValue *value, NCDValMem *mem, NCDValRef *out, int *out_has_placeholders)
 {
+    ASSERT(pdb)
+    ASSERT((NCDValue_Type(value), 1))
+    ASSERT(mem)
+    ASSERT(out)
+    
+    switch (NCDValue_Type(value)) {
+        case NCDVALUE_STRING: {
+            *out = NCDVal_NewStringBin(mem, (const uint8_t *)NCDValue_StringValue(value), NCDValue_StringLength(value));
+            if (NCDVal_IsInvalid(*out)) {
+                goto fail;
+            }
+        } break;
+        
+        case NCDVALUE_LIST: {
+            *out = NCDVal_NewList(mem, NCDValue_ListCount(value));
+            if (NCDVal_IsInvalid(*out)) {
+                goto fail;
+            }
+            
+            for (NCDValue *e = NCDValue_ListFirst(value); e; e = NCDValue_ListNext(value, e)) {
+                NCDValRef vval;
+                if (!convert_value_recurser(pdb, e, mem, &vval, out_has_placeholders)) {
+                    goto fail;
+                }
+                
+                NCDVal_ListAppend(*out, vval);
+            }
+        } break;
+        
+        case NCDVALUE_MAP: {
+            *out = NCDVal_NewMap(mem, NCDValue_MapCount(value));
+            if (NCDVal_IsInvalid(*out)) {
+                goto fail;
+            }
+            
+            for (NCDValue *ekey = NCDValue_MapFirstKey(value); ekey; ekey = NCDValue_MapNextKey(value, ekey)) {
+                NCDValue *eval = NCDValue_MapKeyValue(value, ekey);
+                
+                NCDValRef vkey;
+                NCDValRef vval;
+                if (!convert_value_recurser(pdb, ekey, mem, &vkey, out_has_placeholders) ||
+                    !convert_value_recurser(pdb, eval, mem, &vval, out_has_placeholders)
+                ) {
+                    goto fail;
+                }
+                
+                int res = NCDVal_MapInsert(*out, vkey, vval);
+                ASSERT(res) // we assume different variables get different placeholder ids
+            }
+        } break;
+        
+        case NCDVALUE_VAR: {
+            int plid;
+            if (!NCDPlaceholderDb_AddVariable(pdb, NCDValue_VarName(value), &plid)) {
+                goto fail;
+            }
+            
+            if (NCDVAL_MINIDX + plid >= -1) {
+                goto fail;
+            }
+            
+            *out = NCDVal_NewPlaceholder(mem, plid);
+            *out_has_placeholders = 1;
+        } break;
+        
+        default:
+            goto fail;
+    }
+    
+    return 1;
+    
+fail:
+    return 0;
+}
+
+int NCDInterpBlock_Init (NCDInterpBlock *o, NCDBlock *block, NCDProcess *process, NCDPlaceholderDb *pdb)
+{
+    ASSERT(block)
+    ASSERT(process)
+    ASSERT(pdb)
+    
     if (NCDBlock_NumStatements(block) > INT_MAX) {
         BLog(BLOG_ERROR, "too many statements");
         goto fail0;
@@ -88,6 +169,7 @@ int NCDInterpBlock_Init (NCDInterpBlock *o, NCDBlock *block, NCDProcess *process
     
     o->num_stmts = 0;
     o->prealloc_size = -1;
+    o->process = process;
     
     for (NCDStatement *s = NCDBlock_FirstStatement(block); s; s = NCDBlock_NextStatement(block, s)) {
         ASSERT(NCDStatement_Type(s) == NCDSTATEMENT_REG)
@@ -98,8 +180,22 @@ int NCDInterpBlock_Init (NCDInterpBlock *o, NCDBlock *block, NCDProcess *process
         e->objnames = NULL;
         e->alloc_size = 0;
         
-        if (!NCDInterpValue_Init(&e->ivalue, NCDStatement_RegArgs(s))) {
-            BLog(BLOG_ERROR, "NCDInterpValue_Init failed");
+        NCDValMem mem;
+        NCDValMem_Init(&mem);
+        
+        NCDValRef val;
+        e->arg_has_placeholders = 0;
+        if (!convert_value_recurser(pdb, NCDStatement_RegArgs(s), &mem, &val, &e->arg_has_placeholders)) {
+            BLog(BLOG_ERROR, "convert_value_recurser failed");
+            NCDValMem_Free(&mem);
+            goto loop_fail0;
+        }
+        
+        e->arg_ref = NCDVal_ToSafe(val);
+        
+        if (!NCDValMem_FreeExport(&mem, &e->arg_data, &e->arg_len)) {
+            BLog(BLOG_ERROR, "NCDValMem_FreeExport failed");
+            NCDValMem_Free(&mem);
             goto loop_fail0;
         }
         
@@ -117,15 +213,13 @@ int NCDInterpBlock_Init (NCDInterpBlock *o, NCDBlock *block, NCDProcess *process
         continue;
         
     loop_fail1:
-        NCDInterpValue_Free(&e->ivalue);
+        BFree(e->arg_data);
     loop_fail0:
         goto fail2;
     }
     
     ASSERT(o->num_stmts == num_stmts)
     
-    o->process = process;
-    
     DebugObject_Init(&o->d_obj);
     return 1;
     
@@ -135,7 +229,7 @@ fail2:
         if (e->objnames) {
             free_strings(e->objnames);
         }
-        NCDInterpValue_Free(&e->ivalue);
+        BFree(e->arg_data);
     }
 fail1:
     BFree(o->stmts);
@@ -152,7 +246,7 @@ void NCDInterpBlock_Free (NCDInterpBlock *o)
         if (e->objnames) {
             free_strings(e->objnames);
         }
-        NCDInterpValue_Free(&e->ivalue);
+        BFree(e->arg_data);
     }
     
     NCDInterpBlock__Hash_Free(&o->hash);
@@ -205,13 +299,24 @@ char ** NCDInterpBlock_StatementObjNames (NCDInterpBlock *o, int i)
     return o->stmts[i].objnames;
 }
 
-NCDInterpValue * NCDInterpBlock_StatementInterpValue (NCDInterpBlock *o, int i)
+int NCDInterpBlock_CopyStatementArgs (NCDInterpBlock *o, int i, NCDValMem *out_valmem, NCDValRef *out_val, int *out_has_placeholders)
 {
     DebugObject_Access(&o->d_obj);
     ASSERT(i >= 0)
     ASSERT(i < o->num_stmts)
+    ASSERT(out_valmem)
+    ASSERT(out_val)
+    ASSERT(out_has_placeholders)
+    
+    struct NCDInterpBlock__stmt *e = &o->stmts[i];
     
-    return &o->stmts[i].ivalue;
+    if (!NCDValMem_InitImport(out_valmem, e->arg_data, e->arg_len)) {
+        return 0;
+    }
+    
+    *out_val = NCDVal_FromSafe(out_valmem, e->arg_ref);
+    *out_has_placeholders = e->arg_has_placeholders;
+    return 1;
 }
 
 void NCDInterpBlock_StatementBumpAllocSize (NCDInterpBlock *o, int i, int alloc_size)

+ 10 - 4
ncd/NCDInterpBlock.h

@@ -30,17 +30,23 @@
 #ifndef BADVPN_NCDINTERPBLOCK_H
 #define BADVPN_NCDINTERPBLOCK_H
 
+#include <stddef.h>
+
 #include <misc/debug.h>
 #include <base/DebugObject.h>
 #include <structure/CHash.h>
 #include <ncd/NCDAst.h>
-#include <ncd/NCDInterpValue.h>
+#include <ncd/NCDVal.h>
+#include <ncd/NCDPlaceholderDb.h>
 
 struct NCDInterpBlock__stmt {
     const char *name;
     const char *cmdname;
     char **objnames;
-    NCDInterpValue ivalue;
+    char *arg_data;
+    size_t arg_len;
+    NCDValSafeRef arg_ref;
+    int arg_has_placeholders;
     int alloc_size;
     int prealloc_offset;
     int hash_next;
@@ -62,12 +68,12 @@ typedef struct {
     DebugObject d_obj;
 } NCDInterpBlock;
 
-int NCDInterpBlock_Init (NCDInterpBlock *o, NCDBlock *block, NCDProcess *process) WARN_UNUSED;
+int NCDInterpBlock_Init (NCDInterpBlock *o, NCDBlock *block, NCDProcess *process, NCDPlaceholderDb *pdb) WARN_UNUSED;
 void NCDInterpBlock_Free (NCDInterpBlock *o);
 int NCDInterpBlock_FindStatement (NCDInterpBlock *o, int from_index, const char *name);
 const char * NCDInterpBlock_StatementCmdName (NCDInterpBlock *o, int i);
 char ** NCDInterpBlock_StatementObjNames (NCDInterpBlock *o, int i);
-NCDInterpValue * NCDInterpBlock_StatementInterpValue (NCDInterpBlock *o, int i);
+int NCDInterpBlock_CopyStatementArgs (NCDInterpBlock *o, int i, NCDValMem *out_valmem, NCDValRef *out_val, int *out_has_placeholders) WARN_UNUSED;
 void NCDInterpBlock_StatementBumpAllocSize (NCDInterpBlock *o, int i, int alloc_size);
 int NCDInterpBlock_StatementPreallocSize (NCDInterpBlock *o, int i);
 int NCDInterpBlock_PreallocSize (NCDInterpBlock *o);

+ 5 - 2
ncd/NCDInterpProg.c

@@ -42,8 +42,11 @@
 #include "NCDInterpProg_hash.h"
 #include <structure/CHash_impl.h>
 
-int NCDInterpProg_Init (NCDInterpProg *o, NCDProgram *prog)
+int NCDInterpProg_Init (NCDInterpProg *o, NCDProgram *prog, NCDPlaceholderDb *pdb)
 {
+    ASSERT(prog)
+    ASSERT(pdb)
+    
     if (NCDProgram_NumProcesses(prog) > INT_MAX) {
         BLog(BLOG_ERROR, "too many processes");
         goto fail0;
@@ -68,7 +71,7 @@ int NCDInterpProg_Init (NCDInterpProg *o, NCDProgram *prog)
         e->name = NCDProcess_Name(p);
         e->proc = p;
         
-        if (!NCDInterpBlock_Init(&e->iblock, NCDProcess_Block(p), p)) {
+        if (!NCDInterpBlock_Init(&e->iblock, NCDProcess_Block(p), p, pdb)) {
             BLog(BLOG_ERROR, "NCDInterpBlock_Init failed");
             goto fail2;
         }

+ 1 - 1
ncd/NCDInterpProg.h

@@ -57,7 +57,7 @@ typedef struct {
     DebugObject d_obj;
 } NCDInterpProg;
 
-int NCDInterpProg_Init (NCDInterpProg *o, NCDProgram *prog) WARN_UNUSED;
+int NCDInterpProg_Init (NCDInterpProg *o, NCDProgram *prog, NCDPlaceholderDb *pdb) WARN_UNUSED;
 void NCDInterpProg_Free (NCDInterpProg *o);
 int NCDInterpProg_FindProcess (NCDInterpProg *o, const char *name, NCDProcess **out_proc, NCDInterpBlock **out_iblock) WARN_UNUSED;
 

+ 0 - 237
ncd/NCDInterpValue.c

@@ -1,237 +0,0 @@
-/**
- * @file NCDInterpValue.c
- * @author Ambroz Bizjak <ambrop7@gmail.com>
- * 
- * @section LICENSE
- * 
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the author nor the
- *    names of its contributors may be used to endorse or promote products
- *    derived from this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#include <stdlib.h>
-#include <limits.h>
-
-#include <misc/offset.h>
-#include <misc/split_string.h>
-#include <misc/balloc.h>
-#include <base/BLog.h>
-
-#include "NCDInterpValue.h"
-
-#include <generated/blog_channel_ncd.h>
-
-static int NCDInterpValue_InitString (NCDInterpValue *o, const char *string, size_t len);
-static int NCDInterpValue_InitVar (NCDInterpValue *o, const char *name);
-static void NCDInterpValue_InitList (NCDInterpValue *o);
-static int NCDInterpValue_ListAppend (NCDInterpValue *o, NCDInterpValue v);
-static void NCDInterpValue_InitMap (NCDInterpValue *o);
-static int NCDInterpValue_MapAppend (NCDInterpValue *o, NCDInterpValue key, NCDInterpValue val);
-
-int NCDInterpValue_Init (NCDInterpValue *o, NCDValue *val_ast)
-{
-    switch (NCDValue_Type(val_ast)) {
-        case NCDVALUE_STRING: {
-            return NCDInterpValue_InitString(o, NCDValue_StringValue(val_ast), NCDValue_StringLength(val_ast));
-        } break;
-        
-        case NCDVALUE_VAR: {
-            return NCDInterpValue_InitVar(o, NCDValue_VarName(val_ast));
-        } break;
-        
-        case NCDVALUE_LIST: {
-            NCDInterpValue_InitList(o);
-            
-            for (NCDValue *ve = NCDValue_ListFirst(val_ast); ve; ve = NCDValue_ListNext(val_ast, ve)) {
-                NCDInterpValue e;
-                
-                if (!NCDInterpValue_Init(&e, ve)) {
-                    goto fail_list;
-                }
-                
-                if (!NCDInterpValue_ListAppend(o, e)) {
-                    NCDInterpValue_Free(&e);
-                    goto fail_list;
-                }
-            }
-            
-            return 1;
-            
-        fail_list:
-            NCDInterpValue_Free(o);
-            return 0;
-        } break;
-        
-        case NCDVALUE_MAP: {
-            NCDInterpValue_InitMap(o);
-            
-            for (NCDValue *ekey = NCDValue_MapFirstKey(val_ast); ekey; ekey = NCDValue_MapNextKey(val_ast, ekey)) {
-                NCDValue *eval = NCDValue_MapKeyValue(val_ast, ekey);
-                
-                NCDInterpValue key;
-                NCDInterpValue val;
-                
-                if (!NCDInterpValue_Init(&key, ekey)) {
-                    goto fail_map;
-                }
-                
-                if (!NCDInterpValue_Init(&val, eval)) {
-                    NCDInterpValue_Free(&key);
-                    goto fail_map;
-                }
-                
-                if (!NCDInterpValue_MapAppend(o, key, val)) {
-                    NCDInterpValue_Free(&key);
-                    NCDInterpValue_Free(&val);
-                    goto fail_map;
-                }
-            }
-            
-            return 1;
-            
-        fail_map:
-            NCDInterpValue_Free(o);
-            return 0;
-        } break;
-        
-        default:
-            ASSERT(0);
-            return 0;
-    }
-}
-
-void NCDInterpValue_Free (NCDInterpValue *o)
-{
-    switch (o->type) {
-        case NCDVALUE_STRING: {
-            BFree(o->string);
-        } break;
-        
-        case NCDVALUE_VAR: {
-            free_strings(o->variable_names);
-        } break;
-        
-        case NCDVALUE_LIST: {
-            while (!LinkedList1_IsEmpty(&o->list)) {
-                struct NCDInterpValueListElem *elem = UPPER_OBJECT(LinkedList1_GetFirst(&o->list), struct NCDInterpValueListElem, list_node);
-                NCDInterpValue_Free(&elem->value);
-                LinkedList1_Remove(&o->list, &elem->list_node);
-                free(elem);
-            }
-        } break;
-        
-        case NCDVALUE_MAP: {
-            while (!LinkedList1_IsEmpty(&o->maplist)) {
-                struct NCDInterpValueMapElem *elem = UPPER_OBJECT(LinkedList1_GetFirst(&o->maplist), struct NCDInterpValueMapElem, maplist_node);
-                NCDInterpValue_Free(&elem->key);
-                NCDInterpValue_Free(&elem->val);
-                LinkedList1_Remove(&o->maplist, &elem->maplist_node);
-                free(elem);
-            }
-        } break;
-        
-        default: ASSERT(0);
-    }
-}
-
-int NCDInterpValue_InitString (NCDInterpValue *o, const char *string, size_t len)
-{
-    o->type = NCDVALUE_STRING;
-    
-    if (!(o->string = BAlloc(len))) {
-        BLog(BLOG_ERROR, "BAlloc failed");
-        return 0;
-    }
-    memcpy(o->string, string, len);
-    
-    o->string_len = len;
-    
-    return 1;
-}
-
-int NCDInterpValue_InitVar (NCDInterpValue *o, const char *name)
-{
-    ASSERT(name)
-    
-    o->type = NCDVALUE_VAR;
-    if (!(o->variable_names = split_string(name, '.'))) {
-        return 0;
-    }
-    
-    return 1;
-}
-
-void NCDInterpValue_InitList (NCDInterpValue *o)
-{
-    o->type = NCDVALUE_LIST;
-    LinkedList1_Init(&o->list);
-    o->list_count = 0;
-}
-
-int NCDInterpValue_ListAppend (NCDInterpValue *o, NCDInterpValue v)
-{
-    ASSERT(o->type == NCDVALUE_LIST)
-    
-    if (o->list_count == SIZE_MAX) {
-        BLog(BLOG_ERROR, "size overflow");
-        return 0;
-    }
-    
-    struct NCDInterpValueListElem *elem = malloc(sizeof(*elem));
-    if (!elem) {
-        BLog(BLOG_ERROR, "malloc failed");
-        return 0;
-    }
-    LinkedList1_Append(&o->list, &elem->list_node);
-    elem->value = v;
-    o->list_count++;
-    
-    return 1;
-}
-
-void NCDInterpValue_InitMap (NCDInterpValue *o)
-{
-    o->type = NCDVALUE_MAP;
-    LinkedList1_Init(&o->maplist);
-    o->map_count = 0;
-}
-
-int NCDInterpValue_MapAppend (NCDInterpValue *o, NCDInterpValue key, NCDInterpValue val)
-{
-    ASSERT(o->type == NCDVALUE_MAP)
-    
-    if (o->map_count == SIZE_MAX) {
-        BLog(BLOG_ERROR, "size overflow");
-        return 0;
-    }
-    
-    struct NCDInterpValueMapElem *elem = malloc(sizeof(*elem));
-    if (!elem) {
-        BLog(BLOG_ERROR, "malloc failed");
-        return 0;
-    }
-    LinkedList1_Append(&o->maplist, &elem->maplist_node);
-    elem->key = key;
-    elem->val = val;
-    o->map_count++;
-    
-    return 1;
-}

+ 0 - 72
ncd/NCDInterpValue.h

@@ -1,72 +0,0 @@
-/**
- * @file NCDInterpValue.h
- * @author Ambroz Bizjak <ambrop7@gmail.com>
- * 
- * @section LICENSE
- * 
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the author nor the
- *    names of its contributors may be used to endorse or promote products
- *    derived from this software without specific prior written permission.
- * 
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-#ifndef BADVPN_NCDINTERPVALUE_H
-#define BADVPN_NCDINTERPVALUE_H
-
-#include <stddef.h>
-
-#include <misc/debug.h>
-#include <structure/LinkedList1.h>
-#include <ncd/NCDValue.h>
-
-typedef struct {
-    int type;
-    union {
-        struct {
-            char *string;
-            size_t string_len;
-        };
-        char **variable_names;
-        struct {
-            LinkedList1 list;
-            size_t list_count;
-        };
-        struct {
-            LinkedList1 maplist;
-            size_t map_count;
-        };
-    };
-} NCDInterpValue;
-
-struct NCDInterpValueListElem {
-    LinkedList1Node list_node;
-    NCDInterpValue value;
-};
-
-struct NCDInterpValueMapElem {
-    LinkedList1Node maplist_node;
-    NCDInterpValue key;
-    NCDInterpValue val;
-};
-
-int NCDInterpValue_Init (NCDInterpValue *o, NCDValue *val_ast) WARN_UNUSED;
-void NCDInterpValue_Free (NCDInterpValue *o);
-
-#endif

+ 115 - 0
ncd/NCDPlaceholderDb.c

@@ -0,0 +1,115 @@
+/**
+ * @file NCDPlaceholderDb.c
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+#include <limits.h>
+
+#include <misc/balloc.h>
+#include <misc/split_string.h>
+#include <base/BLog.h>
+
+#include "NCDPlaceholderDb.h"
+
+#include <generated/blog_channel_NCDPlaceholderDb.h>
+
+int NCDPlaceholderDb_Init (NCDPlaceholderDb *o)
+{
+    o->count = 0;
+    o->capacity = 1;
+    
+    if (!(o->arr = BAllocArray(o->capacity, sizeof(o->arr[0])))) {
+        BLog(BLOG_ERROR, "NCDPlaceholderDb_Init failed");
+        return 0;
+    }
+    
+    return 1;
+}
+
+void NCDPlaceholderDb_Free (NCDPlaceholderDb *o)
+{
+    for (size_t i = 0; i < o->count; i++) {
+        free_strings(o->arr[i].varnames);
+    }
+    
+    BFree(o->arr);
+}
+
+int NCDPlaceholderDb_AddVariable (NCDPlaceholderDb *o, const char *varname, int *out_plid)
+{
+    ASSERT(varname)
+    ASSERT(out_plid)
+    ASSERT(o->count <= o->capacity)
+    ASSERT(o->capacity > 0)
+    
+    if (o->count == o->capacity) {
+        if (o->capacity > SIZE_MAX / 2) {
+            BLog(BLOG_ERROR, "too many placeholder entries (cannot resize)");
+            return 0;
+        }
+        size_t newcap = 2 * o->capacity;
+        
+        struct NCDPlaceholderDb__entry *newarr = BAllocArray(newcap, sizeof(newarr[0]));
+        if (!newarr) {
+            BLog(BLOG_ERROR, "BAllocArray failed");
+            return 0;
+        }
+        
+        memcpy(newarr, o->arr, o->count * sizeof(newarr[0]));
+        BFree(o->arr);
+        
+        o->arr = newarr;
+        o->capacity = newcap;
+    }
+    
+    ASSERT(o->count < o->capacity)
+    
+    if (o->count > INT_MAX) {
+        BLog(BLOG_ERROR, "too many placeholder entries (cannot fit integer)");
+        return 0;
+    }
+    
+    char **varnames = split_string(varname, '.');
+    if (!varnames) {
+        BLog(BLOG_ERROR, "split_string failed");
+        return 0;
+    }
+    
+    *out_plid = o->count;
+    o->arr[o->count++].varnames = varnames;
+    
+    return 1;
+}
+
+char ** NCDPlaceholderDb_GetVariable (NCDPlaceholderDb *o, int plid)
+{
+    ASSERT(plid >= 0)
+    ASSERT(plid < o->count)
+    
+    return o->arr[plid].varnames;
+}

+ 92 - 0
ncd/NCDPlaceholderDb.h

@@ -0,0 +1,92 @@
+/**
+ * @file NCDPlaceholderDb.h
+ * @author Ambroz Bizjak <ambrop7@gmail.com>
+ * 
+ * @section LICENSE
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the
+ *    names of its contributors may be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef BADVPN_NCDPLACEHOLDERDB_H
+#define BADVPN_NCDPLACEHOLDERDB_H
+
+#include <stddef.h>
+
+#include <misc/debug.h>
+
+struct NCDPlaceholderDb__entry {
+    char **varnames;
+};
+
+/**
+ * Associates variable placeholder numbers to variable names.
+ * This is populated by {@link NCDInterpBlock_Init} when converting the {@link NCDValue}
+ * objects in the AST to compact representations in {@link NCDValMem}. Variables are
+ * replaced with placeholder identifiers (integers), which this object associates
+ * with their names.
+ * During interpretation, when a statement is being initialized, the compact form held
+ * by {@link NCDInterpBlock} is byte-copied, and placeholders are replaced with the
+ * values of corresponding variables using {@link NCDVal_ReplacePlaceholders}.
+ */
+typedef struct {
+    struct NCDPlaceholderDb__entry *arr;
+    size_t count;
+    size_t capacity;
+} NCDPlaceholderDb;
+
+/**
+ * Initializes the placeholder database.
+ * Returns 1 on success, and 0 on failure.
+ */
+int NCDPlaceholderDb_Init (NCDPlaceholderDb *o) WARN_UNUSED;
+
+/**
+ * Frees the placeholder database.
+ */
+void NCDPlaceholderDb_Free (NCDPlaceholderDb *o);
+
+/**
+ * Adds a variable to the database.
+ * 
+ * @param varname name of the variable (text, including dots). Must not be NULL.
+ * @param out_plid on success, the placeholder identifier will be returned here. Must
+ *                 not be NULL.
+ * @return 1 on success, 0 on failure
+ */
+int NCDPlaceholderDb_AddVariable (NCDPlaceholderDb *o, const char *varname, int *out_plid) WARN_UNUSED;
+
+/**
+ * Retrieves the name of the variable associated with a placeholder identifier.
+ * 
+ * @param plid placeholder identifier; must have been previously provided by
+ *             {@link NCDPlaceholderDb_AddVariable}.
+ * @return name of the variable, split by dots. The returned value points to
+ *         an array of pointers to strings, which is terminated by a NULL
+ *         pointer. These all point to internal data in the placeholder
+ *         database; they must not be modified, and remain valid until the
+ *         database is freed.
+ *         Note that there will always be at least one string in the result.
+ */
+char ** NCDPlaceholderDb_GetVariable (NCDPlaceholderDb *o, int plid);
+
+#endif

+ 216 - 3
ncd/NCDVal.c

@@ -34,11 +34,16 @@
 #include <stdarg.h>
 
 #include <misc/bsize.h>
+#include <misc/balloc.h>
+#include <base/BLog.h>
 
 #include "NCDVal.h"
 
+#include <generated/blog_channel_NCDVal.h>
+
 static void * NCDValMem__BufAt (NCDValMem *o, NCDVal__idx idx)
 {
+    ASSERT(idx >= 0)
     ASSERT(idx < o->used)
     
     return (o->buf ? o->buf : o->fastbuf) + idx;
@@ -94,7 +99,6 @@ static NCDVal__idx NCDValMem__Alloc (NCDValMem *o, bsize_t alloc_size, NCDVal__i
 
 static NCDValRef NCDVal__Ref (NCDValMem *mem, NCDVal__idx idx)
 {
-    ASSERT(idx >= 0 || idx == -1)
     ASSERT(idx == -1 || mem)
     
     NCDValRef ref = {mem, idx};
@@ -120,6 +124,11 @@ static void NCDVal_AssertExternal (NCDValMem *mem, const void *e_buf, size_t e_l
 
 static void NCDVal__AssertValOnly (NCDValMem *mem, NCDVal__idx idx)
 {
+    // placeholders
+    if (idx < -1) {
+        return;
+    }
+    
     ASSERT(idx >= 0)
     ASSERT(idx + sizeof(int) <= mem->used)
     
@@ -204,7 +213,59 @@ void NCDValMem_Init (NCDValMem *o)
 
 void NCDValMem_Free (NCDValMem *o)
 {
-    free(o->buf);
+    NCDVal__AssertMem(o);
+    
+    if (o->buf) {
+        BFree(o->buf);
+    }
+}
+
+int NCDValMem_FreeExport (NCDValMem *o, char **out_data, size_t *out_len)
+{
+    NCDVal__AssertMem(o);
+    ASSERT(out_data)
+    ASSERT(out_len)
+    
+    if (o->buf) {
+        *out_data = o->buf;
+    } else {
+        if (!(*out_data = BAlloc(o->used))) {
+            return 0;
+        }
+        memcpy(*out_data, o->fastbuf, o->used);
+    }
+    
+    *out_len = o->used;
+    
+    return 1;
+}
+
+int NCDValMem_InitImport (NCDValMem *o, const char *data, size_t len)
+{
+    ASSERT(data)
+    ASSERT(len <= NCDVAL_MAXIDX)
+    
+    if (len <= NCDVAL_FASTBUF_SIZE) {
+        memcpy(o->fastbuf, data, len);
+        o->buf = NULL;
+        o->size = NCDVAL_FASTBUF_SIZE;
+    } else {
+        size_t cap = len;
+        if (cap < NCDVAL_FIRST_SIZE) {
+            cap = NCDVAL_FIRST_SIZE;
+        }
+        
+        if (!(o->buf = BAlloc(cap))) {
+            return 0;
+        }
+        
+        memcpy(o->buf, data, len);
+        o->size = cap;
+    }
+    
+    o->used = len;
+    
+    return 1;
 }
 
 void NCDVal_Assert (NCDValRef val)
@@ -216,13 +277,24 @@ int NCDVal_IsInvalid (NCDValRef val)
 {
     NCDVal_Assert(val);
     
-    return val.idx < 0;
+    return (val.idx == -1);
+}
+
+int NCDVal_IsPlaceholder (NCDValRef val)
+{
+    NCDVal_Assert(val);
+    
+    return (val.idx < -1);
 }
 
 int NCDVal_Type (NCDValRef val)
 {
     NCDVal__AssertVal(val);
     
+    if (val.idx < -1) {
+        return NCDVAL_PLACEHOLDER;
+    }
+    
     int *type_ptr = NCDValMem__BufAt(val.mem, val.idx);
     
     return *type_ptr;
@@ -234,6 +306,23 @@ NCDValRef NCDVal_NewInvalid (void)
     return ref;
 }
 
+NCDValRef NCDVal_NewPlaceholder (NCDValMem *mem, int plid)
+{
+    NCDVal__AssertMem(mem);
+    ASSERT(plid >= 0)
+    ASSERT(NCDVAL_MINIDX + plid < -1)
+    
+    NCDValRef ref = {mem, NCDVAL_MINIDX + plid};
+    return ref;
+}
+
+int NCDVal_PlaceholderId (NCDValRef val)
+{
+    ASSERT(NCDVal_IsPlaceholder(val))
+    
+    return (val.idx - NCDVAL_MINIDX);
+}
+
 NCDValRef NCDVal_NewCopy (NCDValMem *mem, NCDValRef val)
 {
     NCDVal__AssertMem(mem);
@@ -295,6 +384,10 @@ NCDValRef NCDVal_NewCopy (NCDValMem *mem, NCDValRef val)
             return copy;
         } break;
         
+        case NCDVAL_PLACEHOLDER: {
+            return NCDVal_NewPlaceholder(mem, NCDVal_PlaceholderId(val));
+        } break;
+        
         default: ASSERT(0);
     }
     
@@ -304,6 +397,119 @@ fail:
     return NCDVal_NewInvalid();
 }
 
+#define V_TYPE(ptr) ((int *)(ptr))
+#define V_LIST(ptr) ((struct NCDVal__list *)(ptr))
+#define V_MAP(ptr) ((struct NCDVal__map *)(ptr))
+
+static int replace_placeholders_recurser (NCDValMem *mem, NCDVal__idx idx, NCDVal_replace_func replace, void *arg)
+{
+    ASSERT(idx >= 0)
+    NCDVal__AssertValOnly(mem, idx);
+    
+    int res;
+    NCDValRef repval;
+    
+    int changed = 0;
+    void *ptr = NCDValMem__BufAt(mem, idx);
+    
+    switch (*V_TYPE(ptr)) {
+        case NCDVAL_STRING: {
+        } break;
+        
+        case NCDVAL_LIST: {
+            NCDVal__idx count = V_LIST(ptr)->count;
+            
+            for (NCDVal__idx i = 0; i < count; i++) {
+                if (V_LIST(ptr)->elem_indices[i] < -1) {
+                    int plid = V_LIST(ptr)->elem_indices[i] - NCDVAL_MINIDX;
+                    if (!replace(arg, plid, mem, &repval) || NCDVal_IsInvalid(repval)) {
+                        return -1;
+                    }
+                    ASSERT(repval.mem == mem)
+                    
+                    ptr = NCDValMem__BufAt(mem, idx);
+                    V_LIST(ptr)->elem_indices[i] = repval.idx;
+                    changed = 1;
+                } else {
+                    if ((res = replace_placeholders_recurser(mem, V_LIST(ptr)->elem_indices[i], replace, arg)) < 0) {
+                        return res;
+                    }
+                    ptr = NCDValMem__BufAt(mem, idx);
+                    changed |= res;
+                }
+            }
+        } break;
+        
+        case NCDVAL_MAP: {
+            NCDVal__idx count = V_MAP(ptr)->count;
+            
+            for (NCDVal__idx i = 0; i < count; i++) {
+                int key_changed = 0;
+                
+                if (V_MAP(ptr)->elems[i].key_idx < -1) {
+                    int plid = V_MAP(ptr)->elems[i].key_idx - NCDVAL_MINIDX;
+                    if (!replace(arg, plid, mem, &repval) || NCDVal_IsInvalid(repval)) {
+                        return -1;
+                    }
+                    ASSERT(repval.mem == mem)
+                    
+                    ptr = NCDValMem__BufAt(mem, idx);
+                    V_MAP(ptr)->elems[i].key_idx = repval.idx;
+                    changed = 1;
+                    key_changed = 1;
+                } else {
+                    if ((res = replace_placeholders_recurser(mem, V_MAP(ptr)->elems[i].key_idx, replace, arg)) < 0) {
+                        return res;
+                    }
+                    ptr = NCDValMem__BufAt(mem, idx);
+                    changed |= res;
+                    key_changed |= res;
+                }
+                
+                if (V_MAP(ptr)->elems[i].val_idx < -1) {
+                    int plid = V_MAP(ptr)->elems[i].val_idx - NCDVAL_MINIDX;
+                    if (!replace(arg, plid, mem, &repval) || NCDVal_IsInvalid(repval)) {
+                        return -1;
+                    }
+                    ASSERT(repval.mem == mem)
+                    
+                    ptr = NCDValMem__BufAt(mem, idx);
+                    V_MAP(ptr)->elems[i].val_idx = repval.idx;
+                    changed = 1;
+                } else {
+                    if ((res = replace_placeholders_recurser(mem, V_MAP(ptr)->elems[i].val_idx, replace, arg)) < 0) {
+                        return res;
+                    }
+                    ptr = NCDValMem__BufAt(mem, idx);
+                    changed |= res;
+                }
+                
+                if (key_changed) {
+                    NCDVal__MapTreeRef ref = {&V_MAP(ptr)->elems[i], NCDVal__MapElemIdx(idx, i)};
+                    NCDVal__MapTree_Remove(&V_MAP(ptr)->tree, mem, ref);
+                    if (!NCDVal__MapTree_Insert(&V_MAP(ptr)->tree, mem, ref, NULL)) {
+                        BLog(BLOG_ERROR, "duplicate key in map");
+                        return -1;
+                    }
+                }
+            }
+        } break;
+        
+        default: ASSERT(0);
+    }
+    
+    return changed;
+}
+
+int NCDVal_ReplacePlaceholders (NCDValRef val, NCDVal_replace_func replace, void *arg)
+{
+    NCDVal__AssertVal(val);
+    ASSERT(!NCDVal_IsPlaceholder(val))
+    ASSERT(replace)
+    
+    return (replace_placeholders_recurser(val.mem, val.idx, replace, arg) >= 0);
+}
+
 int NCDVal_Compare (NCDValRef val1, NCDValRef val2)
 {
     NCDVal__AssertVal(val1);
@@ -380,6 +586,13 @@ int NCDVal_Compare (NCDValRef val1, NCDValRef val2)
             }
         } break;
         
+        case NCDVAL_PLACEHOLDER: {
+            int plid1 = NCDVal_PlaceholderId(val1);
+            int plid2 = NCDVal_PlaceholderId(val2);
+            
+            return (plid1 > plid2) - (plid1 < plid2);
+        } break;
+        
         default:
             ASSERT(0);
             return 0;

+ 79 - 2
ncd/NCDVal.h

@@ -42,6 +42,7 @@
 #define NCDVAL_FIRST_SIZE 256
 
 #define NCDVAL_MAXIDX INT_MAX
+#define NCDVAL_MINIDX INT_MIN
 
 typedef int NCDVal__idx;
 
@@ -105,6 +106,7 @@ typedef struct {
 #define NCDVAL_STRING 1
 #define NCDVAL_LIST 2
 #define NCDVAL_MAP 3
+#define NCDVAL_PLACEHOLDER 4
 
 /**
  * Initializes a value memory object.
@@ -135,10 +137,32 @@ void NCDValMem_Init (NCDValMem *o);
  */
 void NCDValMem_Free (NCDValMem *o);
 
+/**
+ * Attempts to free the value memory object, exporting its data to an external
+ * memory block.
+ * On success, 1 is returned, and *out_data and *out_len are set; *out_data
+ * receives a memory block which should be freed using {@link BFree}.
+ * After the memory object is exported, copies can be created using
+ * {@link NCDValMem_InitImport}. Any value references needed from the original
+ * should be turned to safe references using {@link NCDVal_ToSafe} before
+ * exporting the block, and imported to copies using {@link NCDVal_FromSafe}.
+ * On failure, 0 is returned, and the memory object is unchanged (it should
+ * still be freed using {@link NCDValMem_Free}.
+ */
+int NCDValMem_FreeExport (NCDValMem *o, char **out_data, size_t *out_len) WARN_UNUSED;
+
+/**
+ * Initializes the value memory object as a copy of an external memory block,
+ * which was obtained using {@link NCDValMem_FreeExport}.
+ * The memory block provided is only read, and is copied into this memory object.
+ * Returns 1 on success, 0 on failure.
+ */
+int NCDValMem_InitImport (NCDValMem *o, const char *data, size_t len) WARN_UNUSED;
+
 /**
  * Does nothing.
  * The value reference object must either point to a valid value within a valid
- * memory object, or must be an invalid reference (all functions operating on
+ * memory object, or must be an invalid reference (most functions operating on
  * {@link NCDValRef} implicitly require that).
  */
 void NCDVal_Assert (NCDValRef val);
@@ -148,17 +172,45 @@ void NCDVal_Assert (NCDValRef val);
  */
 int NCDVal_IsInvalid (NCDValRef val);
 
+/**
+ * Determines if a value is a placeholder value.
+ * The value reference must not be an invalid reference.
+ */
+int NCDVal_IsPlaceholder (NCDValRef val);
+
 /**
  * Returns the type of the value reference, which must not be an invalid reference.
- * Possible values are NCDVAL_STRING, NCDVAL_LIST and NCDVAL_MAP.
+ * Possible values are NCDVAL_STRING, NCDVAL_LIST, NCDVAL_MAP and NCDVAL_PLACEHOLDER.
+ * The placeholder type is only used internally in the interpreter for argument
+ * resolution, and is never seen by modules; see {@link NCDVal_NewPlaceholder}.
  */
 int NCDVal_Type (NCDValRef val);
 
 /**
  * Returns an invalid reference.
+ * An invalid reference must not be passed to any function here, except:
+ *   {@link NCDVal_Assert}, {@link NCDVal_IsInvalid}, {@link NCDVal_ToSafe},
+ *   {@link NCDVal_FromSafe}, {@link NCDVal_Moved}.
  */
 NCDValRef NCDVal_NewInvalid (void);
 
+/**
+ * Returns a new placeholder value reference. A placeholder value is a valid value
+ * containing an integer placeholder identifier.
+ * This always succeeds; however, the caller must ensure the identifier is
+ * non-negative and satisfies (NCDVAL_MINIDX + plid < -1).
+ * 
+ * The placeholder type is only used internally in the interpreter for argument
+ * resolution, and is never seen by modules. Also see {@link NCDPlaceholderDb}.
+ */
+NCDValRef NCDVal_NewPlaceholder (NCDValMem *mem, int plid);
+
+/**
+ * Returns the indentifier of a placeholder value.
+ * The value reference must point to a placeholder value.
+ */
+int NCDVal_PlaceholderId (NCDValRef val);
+
 /**
  * Copies a value into the specified memory object. The source
  * must not be an invalid reference, however it may reside in any memory
@@ -168,6 +220,31 @@ NCDValRef NCDVal_NewInvalid (void);
  */
 NCDValRef NCDVal_NewCopy (NCDValMem *mem, NCDValRef val);
 
+/**
+ * Callback used by {@link NCDVal_ReplacePlaceholders} to allow the caller to produce
+ * values of placeholders.
+ * This function should build a new value within the memory object 'mem' (which is
+ * the same as of the value reference whose placeholders are being replaced), and
+ * return a value reference.
+ * On success, it should return 1, writing a valid value reference to *out.
+ * On failure, it can either return 0, or return 1 but write an invalid value reference.
+ * This callback must not access the memory object in any other way than building
+ * new values in it; it must not modify any values ther were already present at the
+ * point it was called.
+ */
+typedef int (*NCDVal_replace_func) (void *arg, int plid, NCDValMem *mem, NCDValRef *out);
+
+/**
+ * Replaces placeholders in a value.
+ * The value reference must point to a valid value, and not a placeholder value.
+ * This will call the callback 'replace', which should build the values to replace
+ * the placeholders.
+ * Returns 1 on success and 0 on failure. On failure, the entire memory object enters
+ * and inconsistent state and must be freed using {@link NCDValMem_Free} before
+ * performing any other operation on it.
+ */
+int NCDVal_ReplacePlaceholders (NCDValRef val, NCDVal_replace_func replace, void *arg);
+
 /**
  * Compares two values, both of which must not be invalid references.
  * Returns -1, 0 or 1.

+ 41 - 89
ncd/ncd.c

@@ -135,6 +135,9 @@ NCDModuleIndex mindex;
 // program AST
 NCDProgram program;
 
+// placeholder database
+NCDPlaceholderDb placeholder_db;
+
 // structure for efficient interpretation
 NCDInterpProg iprogram;
 
@@ -164,6 +167,7 @@ static void process_logfunc (struct process *p);
 static void process_log (struct process *p, int level, const char *fmt, ...);
 static void process_schedule_work (struct process *p);
 static void process_work_job_handler (struct process *p);
+static int replace_placeholders_callback (void *arg, int plid, NCDValMem *mem, NCDValRef *out);
 static void process_advance (struct process *p);
 static void process_wait_timer_handler (struct process *p);
 static int process_find_object (struct process *p, int pos, const char *name, NCDObject *out_object);
@@ -172,7 +176,6 @@ static int process_resolve_variable_expr (struct process *p, int pos, char **nam
 static void statement_logfunc (struct statement *ps);
 static void statement_log (struct statement *ps, int level, const char *fmt, ...);
 static int statement_allocate_memory (struct statement *ps, int alloc_size);
-static int statement_resolve_argument (struct statement *ps, NCDInterpValue *arg, NCDValMem *mem, NCDValRef *out);
 static void statement_instance_func_event (struct statement *ps, int event);
 static int statement_instance_func_getobj (struct statement *ps, const char *objname, NCDObject *out_object);
 static int statement_instance_func_initprocess (struct statement *ps, NCDModuleProcess *mp, const char *template_name);
@@ -314,10 +317,16 @@ int main (int argc, char **argv)
         goto fail4;
     }
     
+    // init placeholder database
+    if (!NCDPlaceholderDb_Init(&placeholder_db)) {
+        BLog(BLOG_ERROR, "NCDPlaceholderDb_Init failed");
+        goto fail4;
+    }
+    
     // init interp program
-    if (!NCDInterpProg_Init(&iprogram, &program)) {
+    if (!NCDInterpProg_Init(&iprogram, &program, &placeholder_db)) {
         BLog(BLOG_ERROR, "NCDInterpProg_Init failed");
-        goto fail4;
+        goto fail4a;
     }
     
     // init module params
@@ -395,6 +404,9 @@ fail5:
     }
     // free interp program
     NCDInterpProg_Free(&iprogram);
+fail4a:
+    // free placeholder database
+    NCDPlaceholderDb_Free(&placeholder_db);
 fail4:
     // free program AST
     NCDProgram_Free(&program);
@@ -960,6 +972,19 @@ void process_work_job_handler (struct process *p)
     }
 }
 
+int replace_placeholders_callback (void *arg, int plid, NCDValMem *mem, NCDValRef *out)
+{
+    struct statement *ps = arg;
+    ASSERT(plid >= 0)
+    ASSERT(mem)
+    ASSERT(out)
+    
+    char **varnames = NCDPlaceholderDb_GetVariable(&placeholder_db, plid);
+    ASSERT(count_strings(varnames) > 0)
+    
+    return process_resolve_variable_expr(ps->p, ps->i, varnames, mem, out);
+}
+
 void process_advance (struct process *p)
 {
     process_assert_pointers(p);
@@ -1015,15 +1040,20 @@ void process_advance (struct process *p)
     // register alloc size for future preallocations
     NCDInterpBlock_StatementBumpAllocSize(p->iblock, p->ap, module->alloc_size);
     
-    // init args mem
-    NCDValMem_Init(&ps->args_mem);
-    
-    // resolve arguments
+    // copy arguments
     NCDValRef args;
-    NCDInterpValue *iargs = NCDInterpBlock_StatementInterpValue(p->iblock, ps->i);
-    if (!statement_resolve_argument(ps, iargs, &ps->args_mem, &args)) {
-        statement_log(ps, BLOG_ERROR, "failed to resolve arguments");
-        goto fail1;
+    int has_placeholders;
+    if (!NCDInterpBlock_CopyStatementArgs(p->iblock, ps->i, &ps->args_mem, &args, &has_placeholders)) {
+        statement_log(ps, BLOG_ERROR, "NCDInterpBlock_CopyStatementArgs failed");
+        goto fail0;
+    }
+    
+    // replace placeholders with values of variables
+    if (has_placeholders) {
+        if (!NCDVal_ReplacePlaceholders(args, replace_placeholders_callback, ps)) {
+            statement_log(ps, BLOG_ERROR, "failed to replace variables in arguments with values");
+            goto fail1;
+        }
     }
     
     // allocate memory
@@ -1194,84 +1224,6 @@ int statement_allocate_memory (struct statement *ps, int alloc_size)
     return 1;
 }
 
-int statement_resolve_argument (struct statement *ps, NCDInterpValue *arg, NCDValMem *mem, NCDValRef *out)
-{
-    ASSERT(ps->i <= ps->p->ap - process_have_child(ps->p))
-    ASSERT(arg)
-    ASSERT(mem)
-    ASSERT(out)
-    
-    switch (arg->type) {
-        case NCDVALUE_STRING: {
-            *out = NCDVal_NewStringBin(mem, (uint8_t *)arg->string, arg->string_len);
-            if (NCDVal_IsInvalid(*out)) {
-                statement_log(ps, BLOG_ERROR, "NCDVal_NewStringBin failed");
-                return 0;
-            }
-        } break;
-        
-        case NCDVALUE_VAR: {
-            if (!process_resolve_variable_expr(ps->p, ps->i, arg->variable_names, mem, out)) {
-                return 0;
-            }
-            if (NCDVal_IsInvalid(*out)) {
-                return 0;
-            }
-        } break;
-        
-        case NCDVALUE_LIST: {
-            *out = NCDVal_NewList(mem, arg->list_count);
-            if (NCDVal_IsInvalid(*out)) {
-                statement_log(ps, BLOG_ERROR, "NCDVal_NewList failed");
-                return 0;
-            }
-            
-            for (LinkedList1Node *n = LinkedList1_GetFirst(&arg->list); n; n = LinkedList1Node_Next(n)) {
-                struct NCDInterpValueListElem *elem = UPPER_OBJECT(n, struct NCDInterpValueListElem, list_node);
-                
-                NCDValRef new_elem;
-                if (!statement_resolve_argument(ps, &elem->value, mem, &new_elem)) {
-                    return 0;
-                }
-                
-                NCDVal_ListAppend(*out, new_elem);
-            }
-        } break;
-        
-        case NCDVALUE_MAP: {
-            *out = NCDVal_NewMap(mem, arg->map_count);
-            if (NCDVal_IsInvalid(*out)) {
-                statement_log(ps, BLOG_ERROR, "NCDVal_NewMap failed");
-                return 0;
-            }
-            
-            for (LinkedList1Node *n = LinkedList1_GetFirst(&arg->maplist); n; n = LinkedList1Node_Next(n)) {
-                struct NCDInterpValueMapElem *elem = UPPER_OBJECT(n, struct NCDInterpValueMapElem, maplist_node);
-                
-                NCDValRef new_key;
-                if (!statement_resolve_argument(ps, &elem->key, mem, &new_key)) {
-                    return 0;
-                }
-                
-                NCDValRef new_val;
-                if (!statement_resolve_argument(ps, &elem->val, mem, &new_val)) {
-                    return 0;
-                }
-                
-                int res = NCDVal_MapInsert(*out, new_key, new_val);
-                if (!res) {
-                    statement_log(ps, BLOG_ERROR, "duplicate map keys");
-                    return 0;
-                }
-            }
-        } break;
-        
-        default: ASSERT(0);
-    }
-    
-    return 1;
-}
-
 void statement_instance_func_event (struct statement *ps, int event)
 {
     ASSERT(ps->state == SSTATE_CHILD || ps->state == SSTATE_ADULT || ps->state == SSTATE_DYING)