New server master
authorRobert Pengelly <robertapengelly@hotmail.com>
Mon, 3 Jun 2024 03:37:59 +0000 (04:37 +0100)
committerRobert Pengelly <robertapengelly@hotmail.com>
Mon, 3 Jun 2024 03:37:59 +0000 (04:37 +0100)
23 files changed:
LICENSE [new file with mode: 0644]
Makefile.pdw [new file with mode: 0644]
Makefile.unix [new file with mode: 0644]
Makefile.w32 [new file with mode: 0644]
README.md [new file with mode: 0644]
cstr.c [new file with mode: 0644]
hashtab.c [new file with mode: 0644]
include/xmake/command.h [new file with mode: 0644]
include/xmake/cstr.h [new file with mode: 0644]
include/xmake/dep.h [new file with mode: 0644]
include/xmake/hashtab.h [new file with mode: 0644]
include/xmake/lib.h [new file with mode: 0644]
include/xmake/read.h [new file with mode: 0644]
include/xmake/report.h [new file with mode: 0644]
include/xmake/rule.h [new file with mode: 0644]
include/xmake/variable.h [new file with mode: 0644]
include/xmake/xmake.h [new file with mode: 0644]
lib.c [new file with mode: 0644]
read.c [new file with mode: 0644]
report.c [new file with mode: 0644]
rule.c [new file with mode: 0644]
variable.c [new file with mode: 0644]
xmake.c [new file with mode: 0644]

diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..fdddb29
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <https://unlicense.org>
diff --git a/Makefile.pdw b/Makefile.pdw
new file mode 100644 (file)
index 0000000..a6256df
--- /dev/null
@@ -0,0 +1,22 @@
+#******************************************************************************
+# @file             Makefile.pdw
+#******************************************************************************
+AS=aswin
+CC=gccwin
+LD=ldwin
+
+COPTS=-S -O2 -fno-common -ansi -I. -I./include -I../pdos/pdpclib -I../pdos/src -D__WIN32__ -D__NOBIVA__ -D__PDOS__
+COBJ=cstr.o hashtab.o lib.o report.o read.o rule.o variable.o xmake.o
+
+all: clean xmake.exe
+
+xmake.exe: $(COBJ)
+  $(LD) -s -o xmake.exe ../pdos/pdpclib/w32start.o $(COBJ) ../pdos/pdpclib/msvcrt.a ../pdos/src/kernel32.a
+
+.c.o:
+  $(CC) $(COPTS) $<
+  $(AS) -o $@ $*.s
+  rm -f $*.s
+
+clean:
+  rm -f *.o xmake.exe
diff --git a/Makefile.unix b/Makefile.unix
new file mode 100644 (file)
index 0000000..988ee8a
--- /dev/null
@@ -0,0 +1,26 @@
+#******************************************************************************
+# @file             Makefile.unix
+#******************************************************************************
+SRCDIR              ?=  $(CURDIR)
+VPATH               :=  $(SRCDIR)
+
+CC                  :=  gcc
+CFLAGS              :=  -D_FILE_OFFSET_BITS=64 -I$(SRCDIR)/include -O2 -Wall -Werror -Wextra -ansi -pedantic -std=c90
+
+CSRC                :=  cstr.c hashtab.c lib.c read.c report.c rule.c variable.c xmake.c
+
+ifeq ($(OS), Windows_NT)
+all: xmake.exe
+
+xmake.exe: $(CSRC)
+       $(CC) $(CFLAGS) -o $@ $^
+else
+all: xmake
+
+xmake: $(CSRC)
+       $(CC) $(CFLAGS) -o $@ $^
+endif
+
+clean:
+       if [ -f xmake ]; then rm -rf xmake; fi
+       if [ -f xmake.exe ]; then rm -rf xmake.exe; fi
diff --git a/Makefile.w32 b/Makefile.w32
new file mode 100644 (file)
index 0000000..7b97bd4
--- /dev/null
@@ -0,0 +1,19 @@
+#******************************************************************************
+# @file             Makefile.w32
+#******************************************************************************
+SRCDIR              ?=  $(CURDIR)
+VPATH               :=  $(SRCDIR)
+
+CC                  :=  gcc
+CFLAGS              :=  -D_FILE_OFFSET_BITS=64 -I$(SRCDIR)/include -O2 -Wall -Werror -Wextra -ansi -pedantic -std=c90
+
+CSRC                :=  cstr.c hashtab.c lib.c read.c report.c rule.c variable.c xmake.c
+
+all: xmake.exe
+
+xmake.exe: $(CSRC)
+       $(CC) $(CFLAGS) -o $@ $^
+
+clean:
+       if exist xmake ( del /q xmake )
+       if exist xmake.exe ( del /q xmake.exe )
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..334430a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,23 @@
+All source code is Public Domain.
+
+## Obtain the source code
+
+    git clone https://git.candlhat.org/xmake.git
+
+## Building
+
+    BSD:
+    
+        Make sure you have gcc and gmake installed then run gmake -f Makefile.unix.
+    
+    Linux:
+    
+        Make sure you have gcc and make installed then run make -f Makefile.unix.
+    
+    macOS:
+    
+        Make sure you have xcode command line tools installed then run make -f Makefile.unix.
+    
+    Windows:
+    
+        Make sure you have mingw installed and the location within your PATH variable then run mingw32-make.exe -f Makefile.w32.
diff --git a/cstr.c b/cstr.c
new file mode 100644 (file)
index 0000000..803ee72
--- /dev/null
+++ b/cstr.c
@@ -0,0 +1,69 @@
+/******************************************************************************
+ * @file            cstr.c
+ *****************************************************************************/
+#include    <stdlib.h>
+#include    <string.h>
+
+#include    <xmake/cstr.h>
+
+extern void *xrealloc (void *__ptr, unsigned long __size);
+
+static void cstr_realloc (CString *cstr, long new_size) {
+
+    long size = cstr->size_allocated;
+    
+    if (size < 8) {
+        size = 8;
+    }
+    
+    while (size < new_size) {
+        size *= 2;
+    }
+    
+    cstr->data = xrealloc (cstr->data, size);
+    cstr->size_allocated = size;
+
+}
+
+void cstr_ccat (CString *cstr, int ch) {
+
+    long size = cstr->size + 1;
+    
+    if (size > cstr->size_allocated) {
+        cstr_realloc (cstr, size);
+    }
+    
+    ((unsigned char *) cstr->data)[size - 1] = ch;
+    cstr->size = size;
+
+}
+
+void cstr_cat (CString *cstr, const char *str, long len) {
+
+    long size;
+    
+    if (len <= 0) {
+        len = strlen (str) + 1 + len;
+    }
+    
+    size = cstr->size + len;
+    
+    if (size > cstr->size_allocated) {
+        cstr_realloc (cstr, size);
+    }
+    
+    memmove (((unsigned char *) cstr->data) + cstr->size, str, len);
+    cstr->size = size;
+
+}
+
+void cstr_new (CString *cstr) {
+    memset (cstr, 0, sizeof (CString));
+}
+
+void cstr_free (CString *cstr) {
+
+    free (cstr->data);
+    cstr_new (cstr);
+
+}
diff --git a/hashtab.c b/hashtab.c
new file mode 100644 (file)
index 0000000..c25447d
--- /dev/null
+++ b/hashtab.c
@@ -0,0 +1,215 @@
+/******************************************************************************
+ * @file            hashtab.c
+ *****************************************************************************/
+#include    <stddef.h>
+#include    <stdlib.h>
+#include    <string.h>
+
+#include    <xmake/hashtab.h>
+
+static struct hashtab_entry *find_entry (struct hashtab_entry *entries, unsigned long capacity, struct hashtab_name *key);
+
+static int adjust_capacity (struct hashtab *table, unsigned long new_capacity) {
+
+    struct hashtab_entry *new_entries, *old_entries;
+    unsigned long i, new_count, old_capacity;
+    
+    if ((new_entries = malloc (sizeof (*new_entries) * new_capacity)) == NULL) {
+        return -2;
+    }
+    
+    for (i = 0; i < new_capacity; i++) {
+    
+        struct hashtab_entry *entry = &new_entries[i];
+        
+        entry->key = NULL;
+        entry->value = NULL;
+    
+    }
+    
+    old_entries = table->entries;
+    old_capacity = table->capacity;
+    
+    new_count = 0;
+    
+    for (i = 0; i < old_capacity; i++) {
+    
+        struct hashtab_entry *entry = &old_entries[i], *dest;
+        
+        if (entry->key == NULL) {
+            continue;
+        }
+        
+        dest = find_entry (new_entries, new_capacity, entry->key);
+        
+        dest->key = entry->key;
+        dest->value = entry->value;
+        
+        new_count++;
+    
+    }
+    
+    free (old_entries);
+    
+    table->capacity = new_capacity;
+    table->count = new_count;
+    table->entries = new_entries;
+    table->used = new_count;
+    
+    return 0;
+
+}
+
+static struct hashtab_entry *find_entry (struct hashtab_entry *entries, unsigned long capacity, struct hashtab_name *key) {
+
+    struct hashtab_entry *tombstone = NULL;
+    unsigned long index;
+    
+    for (index = key->hash % capacity; ; index = (index + 1) % capacity) {
+    
+        struct hashtab_entry *entry = &entries[index];
+        
+        if (entry->key == NULL) {
+        
+            if (entry->value == NULL) {
+            
+                if (tombstone == NULL) {
+                    return entry;
+                }
+                
+                return tombstone;
+            
+            } else if (tombstone == NULL) {
+                tombstone = entry;
+            }
+        
+        } else if (entry->key->bytes == key->bytes) {
+        
+            if (memcmp (entry->key->chars, key->chars, key->bytes) == 0 && entry->key->hash == key->hash) {
+                return entry;
+            }
+        
+        }
+    
+    }
+
+}
+
+static unsigned long hash_string (const void *p, unsigned long length) {
+
+    const unsigned char *str = (const unsigned char *) p;
+    unsigned long i, result = 0;
+    
+    for (i = 0; i < length; i++) {
+        result = (str[i] << 24) + (result >> 19) + (result << 16) + (result >> 13) + (str[i] << 8) - result;
+    }
+    
+    return result;
+
+}
+
+struct hashtab_name *hashtab_alloc_name (const char *str) {
+
+    struct hashtab_name *name;
+    unsigned long bytes = strlen (str), hash = hash_string (str, bytes);
+    
+    if ((name = malloc (sizeof (*name))) == NULL) {
+        return NULL;
+    }
+    
+    name->bytes = bytes;
+    name->chars = str;
+    name->hash = hash;
+    
+    return name;
+
+}
+
+struct hashtab_name *hashtab_get_key (struct hashtab *table, const char *name) {
+
+    struct hashtab_name *key;
+    struct hashtab_entry *entry;
+    
+    if (table == NULL || table->count == 0 || !(key = hashtab_alloc_name (name))) {
+        return 0;
+    }
+    
+    entry = find_entry (table->entries, table->capacity, key);
+    free (key);
+    
+    return entry->key;
+
+}
+
+void *hashtab_get (struct hashtab *table, struct hashtab_name *key) {
+
+    struct hashtab_entry *entry;
+    
+    if (table == NULL || table->count == 0) {
+        return NULL;
+    }
+    
+    entry = find_entry (table->entries, table->capacity, key);
+    
+    if (entry->key == NULL) {
+        return NULL;
+    }
+    
+    return entry->value;
+
+}
+
+int hashtab_put (struct hashtab *table, struct hashtab_name *key, void *value) {
+
+    const long MIN_CAPACITY = 15;
+    
+    struct hashtab_entry *entry;
+    int ret = 0;
+    
+    if (table->used >= table->capacity / 2) {
+    
+        long capacity = table->capacity * 2 - 1;
+        
+        if (capacity < MIN_CAPACITY) {
+            capacity = MIN_CAPACITY;
+        }
+        
+        if ((ret = adjust_capacity (table, capacity))) {
+            return ret;
+        }
+    
+    }
+    
+    entry = find_entry (table->entries, table->capacity, key);
+    
+    if (entry->key == NULL) {
+    
+        table->count++;
+        
+        if (entry->value == NULL) {
+            table->used++;
+        }
+    
+    }
+    
+    entry->key = key;
+    entry->value = value;
+    
+    return 0;
+
+}
+
+void hashtab_remove (struct hashtab *table, struct hashtab_name *key) {
+
+    struct hashtab_entry *entry;
+    
+    if ((entry = find_entry (table->entries, table->capacity, key)) != NULL) {
+    
+        entry->key = NULL;
+        entry->value = NULL;
+        
+        --table->count;
+    
+    }
+
+}
diff --git a/include/xmake/command.h b/include/xmake/command.h
new file mode 100644 (file)
index 0000000..c3ce4e1
--- /dev/null
@@ -0,0 +1,14 @@
+/******************************************************************************
+ * @file            command.h
+ *****************************************************************************/
+#ifndef     _COMMAND_H
+#define     _COMMAND_H
+
+struct commands {
+
+    char *text;
+    unsigned long len;
+
+};
+
+#endif      /* _COMMAND_H */
diff --git a/include/xmake/cstr.h b/include/xmake/cstr.h
new file mode 100644 (file)
index 0000000..6202270
--- /dev/null
@@ -0,0 +1,22 @@
+/******************************************************************************
+ * @file            cstr.h
+ *****************************************************************************/
+#ifndef     _CSTR_H
+#define     _CSTR_H
+
+typedef struct CString {
+
+    long size;
+    void *data;
+    
+    long size_allocated;
+
+} CString;
+
+void cstr_ccat (CString *cstr, int ch);
+void cstr_cat (CString *cstr, const char *str, long len);
+
+void cstr_new (CString *cstr);
+void cstr_free (CString *cstr);
+
+#endif      /* _CSTR_H */
diff --git a/include/xmake/dep.h b/include/xmake/dep.h
new file mode 100644 (file)
index 0000000..b683f81
--- /dev/null
@@ -0,0 +1,14 @@
+/******************************************************************************
+ * @file            dep.h
+ *****************************************************************************/
+#ifndef     _DEP_H
+#define     _DEP_H
+
+struct dep {
+
+    char *name;
+    struct dep *next;
+
+};
+
+#endif      /* _DEP_H */
diff --git a/include/xmake/hashtab.h b/include/xmake/hashtab.h
new file mode 100644 (file)
index 0000000..0c40a69
--- /dev/null
@@ -0,0 +1,36 @@
+/******************************************************************************
+ * @file            hashtab.h
+ *****************************************************************************/
+#ifndef     _HASHTAB_H
+#define     _HASHTAB_H
+
+struct hashtab_name {
+
+    const char *chars;
+    unsigned long bytes, hash;
+
+};
+
+struct hashtab_entry {
+
+    struct hashtab_name *key;
+    void *value;
+
+};
+
+struct hashtab {
+
+    struct hashtab_entry *entries;
+    unsigned long capacity, count, used;
+
+};
+
+struct hashtab_name *hashtab_alloc_name (const char *str);
+struct hashtab_name *hashtab_get_key (struct hashtab *table, const char *name);
+
+int hashtab_put (struct hashtab *table, struct hashtab_name *key, void *value);
+
+void *hashtab_get (struct hashtab *table, struct hashtab_name *key);
+void hashtab_remove (struct hashtab *table, struct hashtab_name *key);
+
+#endif      /* _HASHTAB_H */
diff --git a/include/xmake/lib.h b/include/xmake/lib.h
new file mode 100644 (file)
index 0000000..d3eaf80
--- /dev/null
@@ -0,0 +1,35 @@
+/******************************************************************************
+ * @file            lib.h
+ *****************************************************************************/
+#ifndef     _LIB_H
+#define     _LIB_H
+
+#if     defined (_WIN32)
+# define    PATHSEP                     ";"
+#else
+# define    PATHSEP                     ":"
+#endif
+
+struct option {
+
+    const char *rule;
+    int (*callback) (const char *);
+
+};
+
+char *xstrdup (const char *__s);
+char *xstrndup (const char *__s, unsigned long __len);
+
+int parse_args (char **__args, int __cnt);
+void dynarray_add (void *__ptab, unsigned long *__nb_ptr, void *__data);
+
+void *xmalloc (unsigned long __sz);
+void *xrealloc (void *__ptr, unsigned long __sz);
+
+
+char *get_current_directory (void);
+
+int change_directory (const char *__path);
+int directory_exists (const char *__path);
+
+#endif      /* _LIB_H */
diff --git a/include/xmake/read.h b/include/xmake/read.h
new file mode 100644 (file)
index 0000000..20a1c06
--- /dev/null
@@ -0,0 +1,19 @@
+/******************************************************************************
+ * @file            read.h
+ *****************************************************************************/
+#ifndef     _READ_H
+#define     _READ_H
+
+int read_makefile (const char *filename);
+
+struct nameseq {
+
+    char *name;
+    struct nameseq *next;
+
+};
+
+void *parse_nameseq (char *line, size_t size);
+void record_files (struct nameseq *filenames, char *cmds, size_t cmds_idx, char *depstr);
+
+#endif      /* _READ_H */
diff --git a/include/xmake/report.h b/include/xmake/report.h
new file mode 100644 (file)
index 0000000..4e507f4
--- /dev/null
@@ -0,0 +1,29 @@
+/******************************************************************************
+ * @file            report.h
+ *****************************************************************************/
+#ifndef     _REPORT_H
+#define     _REPORT_H
+
+enum report_type {
+
+    REPORT_WARNING,
+    REPORT_ERROR,
+    REPORT_FATAL_ERROR,
+    REPORT_INTERNAL_ERROR
+
+};
+
+#if     defined (_WIN32)
+# define    COLOR_ERROR                 12
+# define    COLOR_WARNING               13
+# define    COLOR_INTERNAL_ERROR        19
+#else
+# define    COLOR_ERROR                 91
+# define    COLOR_INTERNAL_ERROR        94
+# define    COLOR_WARNING               95
+#endif
+
+unsigned long get_error_count (void);
+void report_at (const char *filename, unsigned long lineno, enum report_type type, const char *fmt, ...);
+
+#endif      /* _REPORT_H */
diff --git a/include/xmake/rule.h b/include/xmake/rule.h
new file mode 100644 (file)
index 0000000..9cfdcbf
--- /dev/null
@@ -0,0 +1,35 @@
+/******************************************************************************
+ * @file            rule.h
+ *****************************************************************************/
+#ifndef     _RULE_H
+#define     _RULE_H
+
+#include    <xmake/command.h>
+#include    <xmake/dep.h>
+
+struct rule {
+
+    char *name;
+    
+    struct dep *deps;
+    struct commands *cmds;
+
+};
+
+struct suffix_rule {
+
+    char *first, *second;
+    struct commands *cmds;
+    
+    struct suffix_rule *next;
+
+};
+
+extern struct suffix_rule *suffix_rules;
+struct rule *rule_find (const char *name);
+
+void rule_add (char *name, struct dep *deps, struct commands *cmds);
+void rule_add_suffix (char *name, struct commands *cmds);
+void rules_init (void);
+
+#endif      /* _RULE_H */
diff --git a/include/xmake/variable.h b/include/xmake/variable.h
new file mode 100644 (file)
index 0000000..4c876bc
--- /dev/null
@@ -0,0 +1,41 @@
+/******************************************************************************
+ * @file            variable.h
+ *****************************************************************************/
+#ifndef     _VARIABLE_H
+#define     _VARIABLE_H
+
+enum variable_flavor {
+
+    VAR_FLAVOR_RECURSIVELY_EXPANDED,
+    VAR_FLAVOR_SIMPLY_EXPANDED,
+    VAR_FLAVOR_IMMEDIATELY_EXPANDED
+
+};
+
+enum variable_origin {
+
+    VAR_ORIGIN_AUTOMATIC,
+    VAR_ORIGIN_COMMAND_LINE,
+    VAR_ORIGIN_FILE
+
+};
+
+struct variable {
+
+    char *name, *value;
+    
+    enum variable_flavor flavor;
+    enum variable_origin origin;
+
+};
+
+struct variable *variable_add (char *name, char *value, enum variable_origin origin);
+struct variable *variable_change (char *name, char *value, enum variable_origin origin);
+struct variable *variable_find (char *name);
+
+char *variable_expand_line (char *line);
+
+void parse_var_line (char *line, enum variable_origin origin);
+void variables_init (void);
+
+#endif      /* _VARIABLE_H */
diff --git a/include/xmake/xmake.h b/include/xmake/xmake.h
new file mode 100644 (file)
index 0000000..78c67be
--- /dev/null
@@ -0,0 +1,31 @@
+/******************************************************************************
+ * @file            xmake.h
+ *****************************************************************************/
+#ifndef     _XMAKE_H
+#define     _XMAKE_H
+
+struct xmake_state {
+
+    char **makefiles;
+    unsigned long nb_makefiles;
+    
+    char **goals;
+    unsigned long nb_goals;
+    
+    char **include_paths;
+    unsigned long nb_include_paths;
+    
+    char **directories;
+    unsigned long nb_directories;
+    
+    char *path;
+    int quiet, no_print;
+
+};
+
+extern struct xmake_state *state;
+extern const char *program_name;
+
+int rule_search_and_build (char *name);
+
+#endif      /* _XMAKE_H */
diff --git a/lib.c b/lib.c
new file mode 100644 (file)
index 0000000..e154e32
--- /dev/null
+++ b/lib.c
@@ -0,0 +1,541 @@
+/******************************************************************************
+ * @file            lib.c
+ *****************************************************************************/
+#include    <assert.h>
+#include    <stdio.h>
+#include    <stdlib.h>
+#include    <string.h>
+
+#include    <xmake/cstr.h>
+#include    <xmake/lib.h>
+#include    <xmake/report.h>
+#include    <xmake/variable.h>
+#include    <xmake/xmake.h>
+
+size_t len = 0;
+
+static int print_help (const char *arg) {
+
+    (void) arg;
+    
+    fprintf (stderr, "Usage: %s [options] [target] ...\n\n", program_name);
+    fprintf (stderr, "Options:\n\n");
+    
+    fprintf (stderr, "    -B                    Ignored.\n");
+    fprintf (stderr, "\n");
+    
+    fprintf (stderr, "    -C DIRECTORY          Change to DIRECTORY before doing anything.\n");
+    fprintf (stderr, "    -I DIRECTORY          Search DIRECTORY for included makefiles.\n");
+    fprintf (stderr, "\n");
+    
+    fprintf (stderr, "    -f FILE               Read FILE as a makefile.\n");
+    fprintf (stderr, "    -s                    Don't echo recipes.\n");
+    fprintf (stderr, "\n");
+    
+    fprintf (stderr, "    --no-print-directory  Don't print the current directory.\n");
+    fprintf (stderr, "    --help                Print this help information.\n");
+    
+    fprintf (stderr, "\n");
+    exit (EXIT_SUCCESS);
+
+}
+
+static int print_version (const char *arg) {
+
+    (void) arg;
+    
+    fprintf (stderr, "%s: version 0.0.1\n", program_name);
+    return 1;
+
+}
+
+static int set_quiet (const char *arg) {
+
+    (void) arg;
+    
+    state->quiet = 1;
+    return 0;
+
+}
+
+static int set_no_print (const char *arg) {
+
+    (void) arg;
+    
+    state->no_print = 1;
+    return 0;
+
+}
+
+static int add_include_path (const char *path) {
+
+    const char *in = path;
+    const char *p;
+    
+    do {
+    
+        int c;
+        
+        CString str;
+        cstr_new (&str);
+        
+        for (p = in; c = *p, c != '\0' && c != PATHSEP[0]; p++) {
+        
+            if (c == '\\') { c = '/'; }
+            cstr_ccat (&str, c);
+        
+        }
+        
+        if (str.size) {
+        
+            if (((char *) str.data)[str.size - 1] != '/') {
+                cstr_ccat (&str, '/');
+            }
+            
+            cstr_ccat (&str, '\0');
+            dynarray_add (&state->include_paths, &state->nb_include_paths, xstrdup (str.data));
+        
+        }
+        
+        cstr_free (&str);
+        in = (p + 1);
+    
+    } while (*p != '\0');
+    
+    return 0;
+
+}
+
+static int ignored (const char *arg) {
+
+    (void) arg;
+    return 0;
+
+}
+
+static int add_directory (const char *path) {
+
+    char *arg = xstrdup (path);
+    
+    while (arg[strlen (arg) - 1] == '/' || arg[strlen (arg) - 1] == '\\') {
+        arg[strlen (arg) - 1] = '\0';
+    }
+    
+    dynarray_add (&state->directories, &state->nb_directories, arg);
+    return 0;
+
+}
+
+static int add_makefile (const char *name) {
+
+    dynarray_add (&state->makefiles, &state->nb_makefiles, xstrdup (name));
+    return 0;
+
+}
+
+static int non_option (const char *arg) {
+
+    if (strchr (arg, '=')) {
+    
+        char *temp = xstrdup (arg);
+        
+        parse_var_line (temp, VAR_ORIGIN_COMMAND_LINE);
+        free (temp);
+    
+    } else {
+        dynarray_add (&state->goals, &state->nb_goals, xstrdup (arg));
+    }
+    
+    return 0;
+
+}
+
+static struct option opts[] = {
+
+    {   "--help",                   &print_help             },
+    {   "-h",                       &print_help             },
+    
+    {   "--version",                &print_version          },
+    
+    {   "--silent",                 &set_quiet              },
+    {   "--quiet",                  &set_quiet              },
+    {   "-s",                       &set_quiet              },
+    
+    {   "--no-print-directory",     &set_no_print           },
+    
+    {   "--no-builtin-rules",       &ignored                },
+    {   "-r",                       &ignored                },
+    
+    {   "--include_dir=",           &add_include_path       },
+    {   "-I:",                      &add_include_path       },
+    
+    {   "--always-make",            &ignored                },
+    {   "-B",                       &ignored                },
+    
+    {   "--directory=",             &add_directory          },
+    {   "-C:",                      &add_directory          },
+    
+    {   "--makefile=",              &add_makefile           },
+    {   "--file=",                  &add_makefile           },
+    {   "-f:",                      &add_makefile           },
+    
+    {   0,                          &non_option             }
+
+};
+
+static int match_rule (const char *rule, const char *arg) {
+
+    const char *ptr;
+    
+    assert (rule);
+    assert (arg);
+    
+    while (*rule == *arg && *rule && *arg) {
+    
+        rule++;
+        arg++;
+    
+    }
+    
+    switch (*rule) {
+    
+        case '\0':
+            return *arg == '\0';
+        
+        case '[':
+        
+            ptr = ++rule;
+            
+            while (*ptr != ']') {
+                ptr++;
+            }
+            
+            return !strncmp (rule, arg, ptr - rule) ? match_rule (ptr + 1, arg + (ptr - rule)) : match_rule (ptr + 1, arg);
+        
+        case '{':
+        
+            do {
+            
+                ptr = ++rule;
+                
+                while (*ptr != '}' && *ptr != '|') {
+                    ptr++;
+                }
+                
+                if (strncmp (rule, arg, ptr - rule) == 0) {
+                
+                    arg = arg + (ptr - rule);
+                    
+                    do {
+                    
+                        rule = ptr;
+                        ptr++;
+                    
+                    } while (*rule != '}');
+                    
+                    return match_rule (ptr, arg);
+                
+                }
+                
+                rule = ptr;
+            
+            } while (*rule == '|');
+            
+            break;
+    
+    }
+    
+    return 0;
+
+}
+
+static int match_arg (struct option opt, int argc, char **argv, int *ret) {
+
+    size_t rule_len, arg_len;
+    assert (opt.callback);
+    
+    *ret = 0;
+    
+    rule_len = strlen (opt.rule);
+    arg_len = strlen (argv[0]);
+    
+    switch (opt.rule[rule_len - 1]) {
+    
+        case ':':
+        
+            rule_len--;
+            
+            if (strncmp (opt.rule, argv[0], rule_len) == 0) {
+            
+                if (arg_len == rule_len) {
+                
+                    if (argc < 2) {
+                    
+                        report_at (program_name, 0, REPORT_ERROR, "missing argument to '%s'", opt.rule);
+                        
+                        *ret = 1;
+                        return 0;
+                    
+                    }
+                    
+                    *ret = opt.callback (argv[1]);
+                    return 2;
+                
+                } else {
+                
+                    assert (arg_len > rule_len);
+                    
+                    *ret = opt.callback (argv[0] + rule_len);
+                    return 1;
+                
+                }
+            
+            }
+            
+            break;
+        
+        case '<':
+        
+            rule_len--;
+            /* fall through */
+        
+        case '=':
+        
+            if (strncmp (opt.rule, argv[0], rule_len) == 0) {
+            
+                if (arg_len == rule_len) {
+                
+                    report_at (program_name, 0, REPORT_ERROR, "missing argument to '%s'", argv[0]);
+                    
+                    *ret = 1;
+                    return 0;
+                
+                }
+                
+                *ret = opt.callback (argv[0] + rule_len);
+                return 1;
+            
+            }
+            
+            break;
+        
+        default:
+        
+            if (match_rule (opt.rule, argv[0])) {
+            
+                *ret = opt.callback (argv[0]);
+                return 1;
+            
+            }
+    
+    }
+    
+    return 0;
+
+}
+
+
+char *xstrdup (const char *__s) {
+
+    char *p = xmalloc (strlen (__s) + 1);
+    
+    strcpy (p, __s);
+    return p;
+
+}
+
+char *xstrndup (const char *__s, unsigned long __len) {
+
+    char *p = xmalloc (__len + 1);
+    
+    memcpy (p, __s, __len);
+    return p;
+
+}
+
+int parse_args (char **__args, int __cnt) {
+
+    struct option *opt, *last;
+    int i, c, ret;
+    
+    for (last = opts; last->rule; last++) {
+        ;
+    }
+    
+    for (i = 1; i < __cnt; ) {
+    
+        if (*(__args[i]) == '-') {
+        
+            for (opt = opts, c = 0; opt->rule && !c; opt++) {
+            
+                c = match_arg (*opt, __cnt - i, __args + i, &ret);
+                
+                if (ret != 0) {
+                    return ret;
+                }
+            
+            }
+            
+            if (c) {
+                i += c;
+            } else {
+            
+                report_at (program_name, 0, REPORT_ERROR, "unrecognized option '%s'", __args[i]);
+                return 1;
+            
+            }
+        
+        } else {
+        
+            if ((ret = last->callback (__args[i])) != 0) {
+                return ret;
+            }
+            
+            i++;
+        
+        }
+    
+    }
+    
+    if (!state->nb_makefiles) {
+        dynarray_add (&state->makefiles, &state->nb_makefiles, xstrdup ("Makefile"));
+    }
+    
+    return 0;
+
+}
+
+void dynarray_add (void *__ptab, unsigned long *__nb_ptr, void *__data) {
+
+    long nb, nb_alloc;
+    void **pp;
+    
+    nb = *__nb_ptr;
+    pp = *(void ***) __ptab;
+    
+    if ((nb & (nb - 1)) == 0) {
+    
+        if (!nb) {
+            nb_alloc = 1;
+        } else {
+            nb_alloc = nb * 2;
+        }
+        
+        pp = xrealloc (pp, nb_alloc * sizeof (void *));
+        *(void ***) __ptab = pp;
+    
+    }
+    
+    pp[nb++] = __data;
+    *__nb_ptr = nb;
+
+}
+
+void *xmalloc (unsigned long __sz) {
+
+    void *ptr = malloc (__sz);
+    
+    if (!ptr && __sz) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "memory full (malloc)");
+        exit (EXIT_FAILURE);
+    
+    }
+    
+    memset (ptr, 0, __sz);
+    return ptr;
+
+}
+
+void *xrealloc (void *__ptr, unsigned long __sz) {
+
+    void *ptr = realloc (__ptr, __sz);
+    
+    if (!ptr && __sz) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "memory full (realloc)");
+        exit (EXIT_FAILURE);
+    
+    }
+    
+    return ptr;
+
+}
+
+
+#if     defined(_WIN32)
+# include   <windows.h>
+
+char *get_current_directory (void) {
+
+    static TCHAR tszBuffer[4096];
+    size_t i;
+    
+    DWORD dwRet;
+    
+    if ((dwRet = GetCurrentDirectory (sizeof (tszBuffer), tszBuffer)) == 0) {
+    
+        report_at (program_name, 0, REPORT_FATAL_ERROR, "failed to get current directory");
+        exit (EXIT_FAILURE);
+    
+    }
+    
+    for (i = 0; i < strlen (tszBuffer); i++) {
+    
+        if (tszBuffer[i] == '\\') {
+            tszBuffer[i] = '/';
+        }
+    
+    }
+    
+    return tszBuffer;
+
+}
+
+int change_directory (const char *__path) {
+    return (SetCurrentDirectory (__path) != 0);
+}
+
+int directory_exists (const char *__path) {
+
+    DWORD dwAttrib = GetFileAttributes (__path);
+    return ((dwAttrib != -1LU) && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
+
+}
+#else
+# include   <dirent.h>
+# include   <unistd.h>
+
+char *get_current_directory (void) {
+
+    static char cwd[4096] = { 0 };
+    memset (cwd, 0, sizeof (cwd));
+    
+    if (getcwd (cwd, sizeof (cwd))) {
+        return cwd;
+    }
+    
+    report_at (program_name, 0, REPORT_FATAL_ERROR, "failed to get current directory");
+    exit (EXIT_FAILURE);
+
+}
+
+int change_directory (const char *__path) {
+    return (chdir (__path) == 0);
+}
+
+int directory_exists (const char *__path) {
+
+    DIR *dir;
+    
+    if ((dir = opendir (__path))) {
+    
+        closedir (dir);
+        return 1;
+    
+    }
+    
+    return 0;
+
+}
+#endif
diff --git a/read.c b/read.c
new file mode 100644 (file)
index 0000000..2a72ad1
--- /dev/null
+++ b/read.c
@@ -0,0 +1,554 @@
+/******************************************************************************
+ * @file            read.c
+ *****************************************************************************/
+#include    <ctype.h>
+#include    <stdio.h>
+#include    <stdlib.h>
+#include    <string.h>
+
+#include    <xmake/command.h>
+#include    <xmake/dep.h>
+#include    <xmake/lib.h>
+#include    <xmake/read.h>
+#include    <xmake/rule.h>
+#include    <xmake/variable.h>
+#include    <xmake/xmake.h>
+
+extern struct variable *default_goal_var;
+
+struct linebuf {
+
+    char *start, *memory;
+    unsigned long size;
+    
+    FILE *f;
+
+};
+
+static char *memory_fgets (char *str, int num, char **source_p) {
+
+    char *source = *source_p;
+    char *p = strchr (source, '\n');
+    
+    if (source[0] == '\0') {
+        return 0;
+    }
+    
+    if (p && p - source < num - 1) {
+    
+        memcpy (str, source, p - source + 1);
+        str[p - source + 1] = '\0';
+        
+        *source_p += p - source + 1;
+        return str;
+    
+    }
+    
+    if ((int) strlen (source) > num - 1) {
+    
+        memcpy (str, source, num - 1);
+        str[num - 1] = '\0';
+        
+        *source_p += num - 1;;
+        return str;
+    
+    }
+    
+    strcpy (str, source);
+    
+    *source_p += strlen (source);
+    return str;
+
+}
+
+static long read_line (struct linebuf *lbuf) {
+
+    long lines_read = 0;
+    
+    char *p = lbuf->start;
+    char *end = lbuf->start + lbuf->size;
+    
+    while (lbuf->f ? fgets (p, end - p, lbuf->f) : memory_fgets (p, end - p, &(lbuf->memory))) {
+    
+        size_t offset;
+        
+        p += strlen (p);
+        offset = p - lbuf->start;
+        
+        lines_read++;
+        
+        if (offset >= 2) {
+        
+            if (p[-1] == '\n' && p[-2] != '\\') {
+            
+                p[-1] = '\0';
+                break;
+            
+            }
+        
+        } else if (offset >= 1) {
+        
+            if (p[-1] == '\n') {
+            
+                p[-1] = '\0';
+                break;
+            
+            }
+        
+        }
+        
+        if (end - p >= 80) {
+            continue;
+        }
+        
+        lbuf->size *= 2;
+        lbuf->start = xrealloc (lbuf->start, lbuf->size);
+        
+        p = lbuf->start + offset;
+        end = lbuf->start + lbuf->size;
+    
+    }
+    
+    return lines_read;
+
+}
+
+static void remove_backslash_newlines (char *line) {
+
+    char *in = line, *out = line;
+    char *p;
+    
+    if (!(p = strchr (in, '\n'))) {
+        return;
+    }
+    
+    do {
+    
+        size_t out_line_len = p - in - 1;
+        
+        if (out != in) {
+            memmove (out, in, out_line_len);
+        }
+        
+        out += out_line_len;
+        in = p + 1;
+        
+        while (isspace ((int) *in)) {
+            in++;
+        }
+        
+        *(out++) = ' ';
+    
+    } while ((p = strchr (in, '\n')));
+    
+    memmove (out, in, strlen (in) + 1);
+
+}
+
+static void remove_comment (char *line) {
+
+    char *p;
+    
+    if ((p = strchr (line, '#'))) {
+        *p = '\0';
+    }
+
+}
+
+static int include_makefile (const char *filename) {
+
+    char *path, *new_name;
+    unsigned long i;
+    
+    if (!read_makefile (filename)) {
+        return 0;
+    }
+    
+    for (i = 0; i < state->nb_include_paths; i++) {
+    
+        path = state->include_paths[i];
+        
+        new_name = xmalloc (strlen (path) + strlen (filename) + 1);
+        sprintf (new_name, "%s%s", path, filename);
+        
+        if (!read_makefile (new_name)) {
+        
+            free (new_name);
+            return 0;
+        
+        }
+        
+        free (new_name);
+    
+    }
+    
+    fprintf (stderr, "%s: *** Failed to include '%s'. Stop.\n", program_name, filename);
+    return 1;
+
+}
+
+static int read_lbuf (struct linebuf *lbuf, int set_default) {
+
+    struct nameseq* filenames = 0;
+    int ret;
+    
+    char *cmds, *clean = 0, *depstr = 0, *q;
+    char *colon, *semicolonp, *commentp;
+    
+    long lines_read;
+    size_t clean_size = 0, cmds_idx = 0, cmds_sz = 256;
+    
+    cmds = xmalloc (cmds_sz);
+    
+#define     record_waiting_files()                                              \
+    do {                                                                        \
+        if (filenames) {                                                        \
+            record_files (filenames, cmds, cmds_idx, depstr);                   \
+            filenames = 0;                                                      \
+        }                                                                       \
+        cmds_idx = 0;                                                           \
+    } while (0)
+    
+    while ((lines_read = read_line (lbuf))) {
+    
+        char *line = lbuf->start;
+        char *p, *src, *dst;
+        
+        if (line[0] == '\0') {
+            continue;
+        }
+        
+        if (line[0] == ' ' || line[0] == '\t') {
+        
+            if (filenames) {
+            
+                while (line[0] == ' ' || line[0] == '\t') {
+                    line++;
+                }
+                
+                if (cmds_idx + strlen (line) + 1 > cmds_sz) {
+                
+                    cmds_sz = (cmds_idx + strlen (line) + 1) * 2;
+                    cmds = xrealloc (cmds, cmds_sz);
+                
+                }
+                
+                src = line;
+                dst = &cmds[cmds_idx];
+                
+                for (; *src; src++, dst++) {
+                
+                    if (src[0] == '\n' && src[-1] == '\\' && src[1] == '\t') {
+                    
+                        *dst = *src;
+                        
+                        src++;
+                        continue;
+                    
+                    }
+                    
+                    *dst = *src;
+                
+                }
+                
+                *dst = '\0';
+                
+                cmds_idx += dst - &cmds[cmds_idx] + 1;
+                cmds[cmds_idx - 1] = '\n';
+                
+                continue;
+            
+            }
+        
+        }
+        
+        if (strlen (line) + 1 > clean_size) {
+        
+            clean_size = strlen (line) + 1;
+            
+            free (clean);
+            clean = xmalloc (clean_size);
+        
+        }
+        
+        strcpy (clean, line);
+        
+        remove_backslash_newlines (clean);
+        remove_comment (clean);
+        
+        p = clean;
+        
+        while (isspace ((int) *p)) {
+            p++;
+        }
+        
+        if (*p == '\0') {
+            continue;
+        }
+        
+        if (strncmp (p, "include", 7) == 0 && (isspace ((int) p[7]) || p[7] == '\0')) {
+        
+            p += 7;
+            
+            for (q = p + strlen (p); q > p && isspace ((int) q[-1]); q--) {
+                ;
+            }
+            
+            *q = '\0';
+            p = line = variable_expand_line (xstrdup (p));
+            
+            while (1) {
+            
+                char saved_ch;
+                
+                for (; isspace ((int) *p); p++) {
+                    ;
+                }
+                
+                for (q = p; *q && !isspace ((int) *q); q++) {
+                    ;
+                }
+                
+                if (q == p) {
+                    break;
+                }
+                
+                saved_ch = *q;
+                *q = '\0';
+                
+                if ((ret = include_makefile (p))) {
+                    return ret;
+                }
+                
+                *q = saved_ch;
+                
+                p = q;
+            
+            }
+            
+            free (line);
+            continue;
+        
+        }
+        
+        if (strchr (p, '=')) {
+        
+            record_waiting_files ();
+            
+            parse_var_line (p, VAR_ORIGIN_FILE);
+            continue;
+        
+        }
+        
+        record_waiting_files ();
+        
+        semicolonp = strchr (line, ';');
+        commentp = strchr (line, '#');
+        
+        if (commentp && semicolonp && (commentp < semicolonp)) {
+        
+            *commentp = '\0';
+            semicolonp = 0;
+        
+        } else if (semicolonp) {
+            *(semicolonp++) = '\0';
+        }
+        
+        remove_backslash_newlines (line);
+        line = variable_expand_line (xstrdup (line));
+        
+        if (!(colon = strchr (line, ':'))) {
+        
+            for (p = line; isspace ((int) *p); p++) {
+                ;
+            }
+            
+            if (*p) {
+                fprintf (stderr, "%s: Missing ':' in rule line!\n", program_name);
+            }
+            
+            free (line);
+            continue;
+        
+        }
+        
+        *colon = '\0';
+        
+        filenames = parse_nameseq (line, sizeof (*filenames));
+        depstr = xstrdup (colon + 1);
+        
+        free (line);
+        
+        if (semicolonp) {
+        
+            if (cmds_idx + strlen (semicolonp) + 1 > cmds_sz) {
+            
+                cmds_sz = (cmds_idx + strlen (semicolonp) + 1) * 2;
+                cmds = xrealloc (cmds, cmds_sz);
+            
+            }
+            
+            memcpy (&(cmds[cmds_idx]), semicolonp, strlen (semicolonp) + 1);
+            cmds_idx += strlen (semicolonp) + 1;
+        
+        }
+        
+        if (set_default && default_goal_var->value[0] == '\0') {
+        
+            struct nameseq *ns;
+            
+            for (ns = filenames; ns; ns = ns->next) {
+            
+                if ((ns->name[0] == '.') && (strchr (ns->name, '\\') == 0) && (strchr (ns->name, '/') == 0)) {
+                    continue;
+                }
+                
+                free (default_goal_var->value);
+                
+                default_goal_var->value = xstrdup (ns->name);
+                break;
+            
+            }
+        
+        }
+    
+    }
+    
+    record_waiting_files ();
+    
+    free (clean);
+    free (cmds);
+    
+    return 0;
+
+}
+
+int read_makefile (const char *filename) {
+
+    struct linebuf lbuf;
+    struct variable *makefile_list;
+    
+    char *new_value;
+    int ret;
+    
+    if (!(lbuf.f = fopen (filename, "r"))) {
+        return 1;
+    }
+    
+    lbuf.size = 256;
+    lbuf.start = xmalloc (lbuf.size);
+    
+    if ((makefile_list = variable_find ("MAKEFILE_LIST"))) {
+    
+        unsigned long old_len = strlen (makefile_list->value);
+        
+        new_value = xmalloc (old_len + 1 + strlen (filename) + 1);
+        sprintf (new_value, "%s %s", makefile_list->value, filename);
+        
+        variable_change ("MAKEFILE_LIST", new_value, VAR_ORIGIN_FILE);
+    
+    } else {
+    
+        new_value = xmalloc (strlen (filename) + 1);
+        sprintf (new_value, "%s", filename);
+        
+        variable_change ("MAKEFILE_LIST", new_value, VAR_ORIGIN_FILE);
+    
+    }
+    
+    ret = read_lbuf (&lbuf, 1);
+    
+    free (lbuf.start);
+    fclose (lbuf.f);
+    
+    return ret;
+
+}
+
+void *parse_nameseq (char *line, size_t size) {
+
+    struct nameseq *start = 0;
+    struct nameseq **pp = &start;
+    
+    char *p, *temp;
+    temp = xmalloc (strlen (line) + 1);
+    
+#define     add_nameseq(_name)                              \
+    do {                                                    \
+        *pp = xmalloc (size);                               \
+        (*pp)->name = xstrdup (_name);                      \
+        pp = &((*pp)->next);                                \
+    } while (0)
+    
+    p = line;
+    
+    while (1) {
+    
+        char *p2;
+        
+        while (isspace ((int) *p)) {
+            p++;
+        }
+        
+        if (*p == '\0') {
+            break;
+        }
+        
+        p2 = p;
+        
+        while (*p2 && !isspace ((int) *p2)) {
+            p2++;
+        }
+        
+        memcpy (temp, p, p2 - p);
+        temp[p2 - p] = '\0';
+        
+        add_nameseq (temp);
+        p = p2;
+    
+    }
+    
+#undef      add_nameseq
+    
+    free (temp);
+    return start;
+
+}
+
+void record_files (struct nameseq *filenames, char *cmds, size_t cmds_idx, char *depstr) {
+
+    struct commands *cmds_p;
+    
+    struct dep *deps;
+    struct nameseq *ns, *old_ns;
+    
+    if (cmds_idx > 0) {
+    
+        cmds_p = xmalloc (sizeof (*cmds_p));
+        
+        cmds_p->text = xstrndup (cmds, cmds_idx);
+        cmds_p->len = cmds_idx;
+    
+    } else {
+        cmds_p = 0;
+    }
+    
+    if (depstr) {
+        deps = parse_nameseq (depstr, sizeof (*deps));
+    } else {
+        deps = 0;
+    }
+    
+    for (ns = filenames, old_ns = 0; ns; old_ns = ns, ns = ns->next, free (old_ns->name), free (old_ns)) {
+    
+        if (ns->name[0] == '.' && !strchr (ns->name, '\\') && !strchr (ns->name, '/')) {
+            rule_add_suffix (ns->name, cmds_p);
+        } else {
+            rule_add (ns->name, deps, cmds_p);
+        }
+    
+    }
+    
+    free (depstr);
+
+}
diff --git a/report.c b/report.c
new file mode 100644 (file)
index 0000000..d36483b
--- /dev/null
+++ b/report.c
@@ -0,0 +1,140 @@
+/******************************************************************************
+ * @file            report.c
+ *****************************************************************************/
+#include    <stdarg.h>
+#include    <stdio.h>
+#include    <string.h>
+
+#include    <xmake/report.h>
+
+unsigned long errors = 0;
+
+#ifndef     __PDOS__
+#if     defined (_WIN32)
+# include   <windows.h>
+static int OriginalConsoleColor = -1;
+#endif
+
+static void reset_console_color (void) {
+
+#if     defined (_WIN32)
+
+    HANDLE hStdError = GetStdHandle (STD_ERROR_HANDLE);
+    
+    if (OriginalConsoleColor == -1) { return; }
+    
+    SetConsoleTextAttribute (hStdError, OriginalConsoleColor);
+    OriginalConsoleColor = -1;
+
+#else
+
+    fprintf (stderr, "\033[0m");
+
+#endif
+
+}
+
+static void set_console_color (int color) {
+
+#if     defined (_WIN32)
+
+    HANDLE hStdError = GetStdHandle (STD_ERROR_HANDLE);
+    WORD wColor;
+    
+    if (OriginalConsoleColor == -1) {
+    
+        CONSOLE_SCREEN_BUFFER_INFO csbi;
+        
+        if (!GetConsoleScreenBufferInfo (hStdError, &csbi)) {
+            return;
+        }
+        
+        OriginalConsoleColor = csbi.wAttributes;
+    
+    }
+    
+    wColor = (OriginalConsoleColor & 0xF0) + (color & 0xF);
+    SetConsoleTextAttribute (hStdError, wColor);
+
+#else
+
+    fprintf (stderr, "\033[%dm", color);
+
+#endif
+
+}
+#endif
+
+static void output_message (const char *filename, unsigned long lineno, enum report_type type, const char *fmt, va_list ap) {
+
+    if (filename) {
+    
+        if (lineno == 0) {
+            fprintf (stderr, "%s: ", filename);
+        } else {
+            fprintf (stderr, "%s:", filename);
+        }
+    
+    }
+    
+    if (lineno > 0) {
+        fprintf (stderr, "%lu: ", lineno);
+    }
+    
+    if (type == REPORT_ERROR || type == REPORT_FATAL_ERROR) {
+    
+#ifndef     __PDOS__
+        set_console_color (COLOR_ERROR);
+#endif
+        
+        if (type == REPORT_ERROR) {
+            fprintf (stderr, "error:");
+        } else {
+            fprintf (stderr, "fatal error:");
+        }
+    
+    } else if (type == REPORT_INTERNAL_ERROR) {
+    
+#ifndef     __PDOS__
+        set_console_color (COLOR_INTERNAL_ERROR);
+#endif
+        
+        fprintf (stderr, "internal error:");
+    
+    } else if (type == REPORT_WARNING) {
+    
+#ifndef     __PDOS__
+        set_console_color (COLOR_WARNING);
+#endif
+        
+        fprintf (stderr, "warning:");
+    
+    }
+    
+#ifndef     __PDOS__
+    reset_console_color ();
+#endif
+    
+    fprintf (stderr, " ");
+    vfprintf (stderr, fmt, ap);
+    fprintf (stderr, "\n");
+    
+    if (type != REPORT_WARNING) {
+        ++errors;
+    }
+
+}
+
+unsigned long get_error_count (void) {
+    return errors;
+}
+
+void report_at (const char *filename, unsigned long lineno, enum report_type type, const char *fmt, ...) {
+
+    va_list ap;
+    
+    va_start (ap, fmt);
+    output_message (filename, lineno, type, fmt, ap);
+    va_end (ap);
+
+}
diff --git a/rule.c b/rule.c
new file mode 100644 (file)
index 0000000..3f75db0
--- /dev/null
+++ b/rule.c
@@ -0,0 +1,67 @@
+/******************************************************************************
+ * @file            rule.c
+ *****************************************************************************/
+#include    <string.h>
+
+#include    <xmake/command.h>
+#include    <xmake/dep.h>
+#include    <xmake/hashtab.h>
+#include    <xmake/lib.h>
+#include    <xmake/rule.h>
+
+static struct hashtab hashtab_rules = { 0 };
+struct suffix_rule *suffix_rules = 0;
+
+struct rule *rule_find (const char *name) {
+
+    struct hashtab_name *key;
+    
+    if (!(key = hashtab_get_key (&hashtab_rules, name))) {
+        return 0;
+    }
+    
+    return hashtab_get (&hashtab_rules, key);
+
+}
+
+void rule_add (char *name, struct dep *deps, struct commands *cmds) {
+
+    struct hashtab_name *key;
+    struct rule *r;
+    
+    if (!(key = hashtab_alloc_name (xstrdup (name)))) {
+        return;
+    }
+    
+    r = xmalloc (sizeof (*r));
+    
+    r->name = xstrdup (name);
+    r->deps = deps;
+    r->cmds = cmds;
+    
+    hashtab_put (&hashtab_rules, key, r);
+
+}
+
+void rule_add_suffix (char *name, struct commands *cmds) {
+
+    struct suffix_rule *s = xmalloc (sizeof (*s));
+    char *p;
+    
+    if ((p = strchr (name + 1, '.'))) {
+    
+        s->second = xstrdup (p);
+        *p = '\0';
+    
+    }
+    
+    s->first = xstrdup (name);
+    
+    s->cmds = cmds;
+    s->next = suffix_rules;
+    
+    suffix_rules = s;
+
+}
+
+void rules_init (void) {}
diff --git a/variable.c b/variable.c
new file mode 100644 (file)
index 0000000..1bfa197
--- /dev/null
@@ -0,0 +1,480 @@
+/******************************************************************************
+ * @file            variable.c
+ *****************************************************************************/
+#include    <ctype.h>
+#include    <stdio.h>
+#include    <stdlib.h>
+#include    <string.h>
+
+#include    <xmake/hashtab.h>
+#include    <xmake/lib.h>
+#include    <xmake/report.h>
+#include    <xmake/variable.h>
+#include    <xmake/xmake.h>
+
+static struct hashtab hashtab_vars = { 0 };
+
+static char *variable_suffix_replace (char *body, const char *from_s, const char *to_s) {
+
+    char *new_body = xstrdup (body);
+    char *p;
+    
+    while ((p = strstr (new_body, from_s))) {
+    
+        if (strlen (from_s) == strlen (to_s)) {
+            memcpy (p, to_s, strlen (to_s));
+        } else if (strlen (from_s) > strlen (to_s)) {
+        
+            size_t rem = strlen (from_s) - strlen (to_s);
+            memcpy (p, to_s, strlen (to_s));
+            
+            while (rem--) {
+                p[strlen (to_s) + rem] = ' ';
+            }
+        
+        } else {
+        
+            size_t rem = strlen (to_s) - strlen (from_s);
+            
+            new_body = xrealloc (new_body, strlen (new_body) + rem);
+            memmove (p + rem, p, strlen (p) + 1);
+            memcpy (p, to_s, strlen (to_s));
+        
+        }
+    
+    }
+    
+    return new_body;
+
+}
+
+struct variable *variable_add (char *name, char *value, enum variable_origin origin) {
+
+    struct hashtab_name *key;
+    struct variable *var;
+    
+    if (!(key = hashtab_alloc_name (name))) {
+    
+        report_at (program_name, 0, REPORT_ERROR, "failed to allocate memory for name '%s'", name);
+        exit (EXIT_FAILURE);
+    
+    }
+    
+    var = xmalloc (sizeof (*var));
+    var->flavor = VAR_FLAVOR_RECURSIVELY_EXPANDED;
+    var->name = name;
+    var->origin = origin;
+    var->value = value;
+    
+    if (hashtab_put (&hashtab_vars, key, var)) {
+    
+        report_at (program_name, 0, REPORT_FATAL_ERROR, "failed to insert variable '%s' into hashtab", var->name);
+        exit (EXIT_FAILURE);
+    
+    }
+    
+    return var;
+
+}
+
+struct variable *variable_change (char *name, char *value, enum variable_origin origin) {
+
+    struct variable *var;
+    
+    if (!(var = variable_find (name))) {
+        return variable_add (xstrdup (name), value, origin);
+    }
+    
+    switch (origin) {
+    
+        case VAR_ORIGIN_AUTOMATIC:
+        case VAR_ORIGIN_COMMAND_LINE:
+        
+            break;
+        
+        case VAR_ORIGIN_FILE:
+        
+            if (var->origin == VAR_ORIGIN_FILE) {
+                break;
+            }
+            
+            free (value);
+            return 0;
+    
+    }
+    
+    free (var->value);
+    
+    var->value = value;
+    var->origin = origin;
+    
+    return var;
+
+}
+
+struct variable *variable_find (char *name) {
+
+    struct hashtab_name *key;
+    
+    if (!(key = hashtab_get_key (&hashtab_vars, name))) {
+        return 0;
+    }
+    
+    return hashtab_get (&hashtab_vars, key);
+
+}
+
+char *variable_expand_line (char *line) {
+
+    size_t pos = 0;
+    
+    while (line[pos]) {
+    
+        if (line[pos] == '$') {
+        
+            char *new, *replacement = "";
+            char *p_after_variable;
+            
+            struct variable *var = 0;
+            char *alloc_replacement = 0;
+            
+            if (line[pos + 1] == '$') {
+            
+                p_after_variable = line + pos + 2;
+                pos += 1;
+            
+            } else if (line[pos + 1] == '(' || line[pos + 1] == '{') {
+            
+                char *body = line + pos + 2;
+                char *q = body, *content;
+                
+                int paren_inbalance = 1;
+                char opening_paren = line[pos + 1];
+                
+                while (paren_inbalance) {
+                
+                    if (*q == opening_paren) {
+                        paren_inbalance++;
+                    } else if (*q == ')' && opening_paren == '(') {
+                        paren_inbalance--;
+                    } else if (*q == '}' && opening_paren == '{') {
+                        paren_inbalance--;
+                    } else if (*q == '\0') {
+                    
+                        fprintf (stderr, "%s: *** unterminated variable reference. Stop.\n", program_name);
+                        exit (EXIT_FAILURE);
+                    
+                    }
+                    
+                    q++;
+                
+                }
+                
+                q--;
+                
+                p_after_variable = q + 1;
+                content = variable_expand_line (xstrndup (body, q - body));
+                
+                if ((q = strchr (content, '='))) {
+                
+                    char *colon = strchr (content, ':');
+                    
+                    if (colon && colon != content && colon < q) {
+                    
+                        char *from, *to, *p;
+                        *colon = '\0';
+                        
+                        var = variable_find (content);
+                        
+                        for (from = colon + 1; isspace ((int) *from); from++) {
+                            ;
+                        }
+                        
+                        for (p = q; p != from && isspace ((int) p[-1]); p--) {
+                            ;
+                        }
+                        
+                        *p = '\0';
+                        
+                        for (to = q + 1; isspace ((int) *to); to++) {
+                            ;
+                        }
+                        
+                        for (p = to + strlen (to); p != to && isspace ((int) p[-1]); p--) {
+                            ;
+                        }
+                        
+                        *p = '\0';
+                        
+                        if (*from && var) {
+                            alloc_replacement = variable_suffix_replace (var->value, from, to);
+                        }
+                    
+                    }
+                
+                } else {
+                    var = variable_find (content);
+                }
+                
+                free (content);
+            
+            } else if (line[pos + 1]) {
+            
+                char name[2] = { 0, 0 };
+                
+                p_after_variable = line + pos + 2;
+                name[0] = line[pos + 1];
+                
+                var = variable_find (name);
+            
+            } else {
+                p_after_variable = line + pos + 1;
+            }
+            
+            if (var) {
+                replacement = var->value;
+            }
+            
+            if (alloc_replacement) {
+                replacement = alloc_replacement;
+            }
+            
+            new = xmalloc (pos + strlen (replacement) + strlen (p_after_variable) + 1);
+            
+            memcpy (new, line, pos);
+            memcpy (new + pos, replacement, strlen (replacement));
+            memcpy (new + pos + strlen (replacement), p_after_variable, strlen (p_after_variable) + 1);
+            
+            free (line);
+            line = new;
+            
+            if (!alloc_replacement && var && var->flavor == VAR_FLAVOR_SIMPLY_EXPANDED) {
+                pos += strlen (replacement);
+            }
+            
+            free (alloc_replacement);
+            continue;
+            
+        
+        }
+        
+        pos++;
+    
+    }
+    
+    return line;
+
+}
+
+void parse_var_line (char *line, enum variable_origin origin) {
+
+    enum {
+        VAR_ASSIGN,
+        VAR_CONDITIONAL_ASSIGN,
+        VAR_APPEND,
+        VAR_SHELL
+    } opt = VAR_ASSIGN;
+    
+    enum variable_flavor flavor = VAR_FLAVOR_RECURSIVELY_EXPANDED;
+    struct variable *var;
+    
+    char *var_name, *new_value;
+    char *equals_sign, *p;
+    
+    if (!(equals_sign = strchr (line, '='))) {
+    
+        fprintf (stderr, "+++ invalid variable definition!\n");
+        return;
+    
+    }
+    
+    p = equals_sign;
+    
+    switch (p - line) {
+    
+        default:
+        
+            if (p[-1] == ':' && p[-2] == ':' && p[-3] == ':') {
+            
+                flavor = VAR_FLAVOR_IMMEDIATELY_EXPANDED;
+                
+                p -= 3;
+                break;
+            
+            }
+            
+            /* fall through */
+        
+        case 2:
+        
+            if (p[-1] == ':' && p[-2] == ':') {
+            
+                flavor = VAR_FLAVOR_SIMPLY_EXPANDED;
+                
+                p -= 2;
+                break;
+            
+            }
+            
+            /* fall through */
+        
+        case 1:
+        
+            if (p[-1] == ':') {
+            
+                flavor = VAR_FLAVOR_SIMPLY_EXPANDED;
+                
+                p--;
+                break;
+            
+            }
+            
+            if (p[-1] == '?') {
+            
+                opt = VAR_CONDITIONAL_ASSIGN;
+                
+                p--;
+                break;
+            
+            }
+            
+            if (p[-1] == '+') {
+            
+                opt = VAR_APPEND;
+                
+                p--;
+                break;
+            
+            }
+            
+            if (p[-1] == '!') {
+            
+                opt = VAR_SHELL;
+                
+                p--;
+                break;
+            
+            }
+            
+            break;
+        
+        case 0:
+        
+            break;
+    
+    }
+    
+    for (; p > line && isspace ((int) p[-1]); p--) {
+        ;
+    }
+    
+    var_name = variable_expand_line (xstrndup (line, p - line));
+    
+    if (*var_name == '\0') {
+    
+        fprintf (stderr, "%s: *** empty variable name. Stop.\n", program_name);
+        exit (EXIT_FAILURE);
+    
+    }
+    
+    var = variable_find (var_name);
+    
+    if (opt == VAR_CONDITIONAL_ASSIGN && var) {
+    
+        free (var_name);
+        return;
+    
+    }
+    
+    for (p = equals_sign; isspace ((int) p[1]); p++) {
+        ;
+    }
+    
+    new_value = xstrdup (p + 1);
+    
+    if (opt == VAR_ASSIGN || opt == VAR_CONDITIONAL_ASSIGN) {
+    
+        switch (flavor) {
+        
+            case VAR_FLAVOR_RECURSIVELY_EXPANDED:
+            
+                break;
+            
+            case VAR_FLAVOR_SIMPLY_EXPANDED:
+            
+                new_value = variable_expand_line (new_value);
+                break;
+            
+            case VAR_FLAVOR_IMMEDIATELY_EXPANDED: {
+            
+                size_t dollar_count;
+                char *temp, *p2;
+                
+                new_value = variable_expand_line (new_value);
+                
+                for (dollar_count = 0, p = new_value; *p; p++) {
+                
+                    if (*p == '$') {
+                        dollar_count++;
+                    }
+                
+                }
+                
+                temp = xmalloc (strlen (new_value) + 1 + dollar_count);
+                
+                for (p = new_value, p2 = temp; *p; p++, p2++) {
+                
+                    *p2 = *p;
+                    
+                    if (*p == '$') {
+                    
+                        p2[1] = '$';
+                        p2++;
+                    
+                    }
+                    
+                    *p2 = '\0';
+                    
+                    free (new_value);
+                    new_value = temp;
+                
+                }
+                
+                break;
+            
+            }
+        
+        }
+    
+    } else if (opt == VAR_APPEND) {
+    
+        struct variable *var;
+        
+        if ((var = variable_find (var_name))) {
+        
+            char *temp = xstrdup (new_value);
+            free (new_value);
+            
+            new_value = xmalloc (strlen (var->value) + 1 + strlen (temp) + 1);
+            sprintf (new_value, "%s %s", var->value, temp);
+            
+            free (temp);
+        
+        }
+    
+    } else if (opt == VAR_SHELL) {
+    
+        fprintf (stderr, "+++internal error: != not supported yet. Stop.\n");
+        exit (EXIT_FAILURE);
+    
+    }
+    
+    if ((var = variable_change (var_name, new_value, origin))) {
+        var->flavor = flavor;
+    }
+    
+    free (var_name);
+
+}
+
+void variables_init (void) {}
diff --git a/xmake.c b/xmake.c
new file mode 100644 (file)
index 0000000..85159c5
--- /dev/null
+++ b/xmake.c
@@ -0,0 +1,625 @@
+/******************************************************************************
+ * @file            xmake.c
+ *****************************************************************************/
+#include    <ctype.h>
+#include    <stdio.h>
+#include    <stdlib.h>
+#include    <string.h>
+
+#include    <xmake/cstr.h>
+#include    <xmake/lib.h>
+#include    <xmake/read.h>
+#include    <xmake/rule.h>
+#include    <xmake/variable.h>
+#include    <xmake/xmake.h>
+
+struct variable *default_goal_var = 0;
+
+struct xmake_state *state = 0;
+const char *program_name = 0;
+
+#if     defined (_WIN32) || defined (__WIN32__)
+const char *os_name = "Windows_NT";
+#elif   defined (__PDOS386__)
+const char *os_name = "PDOS";
+#elif   defined (__MSDOS__)
+const char *os_name = "MSDOS";
+#elif   defined (__APPLE__)
+const char *os_name = "Apple";
+#elif   defined (__linux__)
+const char *os_name = "Linux";
+#else
+const char *os_name = "Unix";
+#endif
+
+static int doing_inference_rule_commands = 0;
+
+static int rule_run_command (const char *name, char *p, char *q) {
+
+    int is_quiet = state->quiet, is_ignoring_errors = 0;
+    char *new_cmds, *s;
+    
+    *q = '\0';
+    new_cmds = xstrdup (p);
+    
+    *q = '\n';
+    new_cmds = variable_expand_line (new_cmds);
+    
+    s = new_cmds;
+    
+    while (isspace ((int) *s) || *s == '-' || *s == '@') {
+    
+        if (*s == '-') {
+            is_quiet = 1;
+        }
+        
+        if (*s == '@') {
+            is_ignoring_errors = 1;
+        }
+        
+        s++;
+    
+    }
+    
+    if (!is_quiet) {
+        printf ("%s\n", new_cmds);
+    }
+    
+    /*if (!dry_run) */{
+    
+        int error = system (s);
+        
+        if (!is_ignoring_errors && error) {
+        
+            fprintf (stderr, "[%s] Error %d\n", name, error);
+            return 1;
+        
+        }
+    
+    }
+    
+    free (new_cmds);
+    return 0;
+
+}
+
+static char *parse_command (char *text) {
+
+    char *cwd = get_current_directory ();
+    char *ret, *p;
+    
+    if (strncmp (text, "./", 2) && strncmp (text, "../", 3)) {
+        return text;
+    }
+    
+    while (1) {
+    
+        if (strncmp (text, "./", 2) && strncmp (text, "../", 3)) {
+            break;
+        }
+        
+        if (strncmp (text, "./", 2) == 0) {
+            text += 2;
+        } else if (strncmp (text, "../", 3) == 0) {
+        
+            text += 3;
+            
+            if ((p = strrchr (cwd, '/'))) {
+                *p = '\0';
+            }
+        
+        }
+    
+    }
+    
+    ret = xmalloc (strlen (cwd) + 1 + strlen (text));
+    sprintf (ret, "%s/%s", cwd, text);
+    
+    return ret;
+
+}
+
+static int run_commands (struct commands *cmds, char *name) {
+
+    char *p = parse_command (cmds->text), *q;
+    int error;
+    
+    while (1) {
+    
+        q = strchr (p, '\n');
+        
+        if (q != cmds->text) {
+        
+            while (q && q[-1] == '\\') {
+                q = strchr (q + 1, '\n');
+            }
+        
+        }
+        
+        if (!q) { break; };
+        
+        if ((error = rule_run_command (name, p, q)) < 0) {
+            return error;
+        }
+        
+        p = q + 1;
+    
+    }
+    
+    return 0;
+
+}
+
+static int rule_use (struct rule *r, char *name) {
+
+    struct dep *dep;
+    
+    char *p, *star_name, *lesser_name;
+    int ret;
+    
+    CString str;
+    cstr_new (&str);
+    
+    for (dep = r->deps; dep; dep = dep->next) {
+    
+        cstr_cat (&str, dep->name, strlen (dep->name));
+        
+        if ((ret = rule_search_and_build (dep->name))) {
+            return ret;
+        }
+        
+        if (dep->next) {
+            cstr_ccat (&str, ' ');
+        }
+    
+    }
+    
+    cstr_ccat (&str, '\0');
+    
+    if (!r->cmds) {
+    
+        cstr_free (&str);
+        return 0;
+    
+    }
+    
+    doing_inference_rule_commands = 0;
+    
+    if (r->deps) {
+        lesser_name = xstrdup (r->deps->name);
+    } else {
+        lesser_name = xstrdup ("");
+    }
+    
+    star_name = xstrdup (name);
+    
+    if ((p = strrchr (star_name, '.'))) {
+        *p = '\0';
+    }
+    
+    variable_change ("@", xstrdup (name), VAR_ORIGIN_AUTOMATIC);
+    variable_change ("<", lesser_name, VAR_ORIGIN_AUTOMATIC);
+    variable_change ("*", star_name, VAR_ORIGIN_AUTOMATIC);
+    variable_change ("^", xstrdup (str.data), VAR_ORIGIN_AUTOMATIC);
+    
+    return run_commands (r->cmds, name);
+
+}
+
+static int suffix_rule_use (struct suffix_rule *s, char *name) {
+
+    char *lesser_name, *star_name;
+    char *p;
+    
+    if (!s->cmds) { return 0; }
+    doing_inference_rule_commands = 1;
+    
+    lesser_name = xmalloc (strlen (name) + strlen (s->first) + 1);
+    memcpy (lesser_name, name, strlen (name) + 1);
+    
+    if ((p = strrchr (lesser_name, '.'))) {
+        *p = '\0';
+    }
+    
+    strcat (lesser_name, s->first);
+    star_name = xstrdup (name);
+    
+    if ((p = strrchr (star_name, '.'))) {
+        *p = '\0';
+    }
+    
+    variable_change ("<", lesser_name, VAR_ORIGIN_AUTOMATIC);
+    variable_change ("*", star_name, VAR_ORIGIN_AUTOMATIC);
+    
+    return run_commands (s->cmds, name);
+
+}
+
+static int single_suffix_rule_use (struct suffix_rule *s, char *name) {
+
+    char *lesser_name, *star_name;
+    
+    if (!s->cmds) { return 0; }
+    doing_inference_rule_commands = 1;
+    
+    lesser_name = xmalloc (strlen (name) + strlen (s->first) + 1);
+    strcpy (lesser_name, name);
+    strcat (lesser_name, s->first);
+    
+    star_name = xstrdup (name);
+    
+    variable_change ("<", lesser_name, VAR_ORIGIN_AUTOMATIC);
+    variable_change ("*", star_name, VAR_ORIGIN_AUTOMATIC);
+    
+    return run_commands (s->cmds, name);
+
+}
+
+static int file_exists (char *name) {
+
+    FILE *f;
+    
+    if (!(f = fopen (name, "r"))) {
+        return 0;
+    }
+    
+    fclose (f);
+    return 1;
+
+}
+
+static char *find_target (char *target) {
+
+    struct variable *vpath_var;
+    char *vpath;
+    
+    if (file_exists (target)) {
+        return target;
+    }
+    
+    if (!(vpath_var = variable_find ("VPATH"))) {
+        return 0;
+    }
+    
+    vpath = variable_expand_line (xstrdup (vpath_var->value));
+    
+    while (vpath && *vpath) {
+    
+        char *vpath_part, *new_target;
+        char saved_ch;
+        
+        while (*vpath && (isspace ((int) *vpath) || (*vpath == ';'))) {
+            vpath++;
+        }
+        
+        vpath_part = vpath;
+        
+        while (*vpath && !isspace ((int) *vpath) && *vpath != ';') {
+            vpath++;
+        }
+        
+        saved_ch = *vpath;
+        *vpath = '\0';
+        
+        new_target = xmalloc (strlen (vpath_part) + 1 + strlen (target) + 1);
+        strcpy (new_target, vpath_part);
+        
+        if (strchr (vpath_part, '\\')) {
+            strcat (new_target, "\\");
+        } else {
+            strcat (new_target, "/");
+        }
+        
+        strcat (new_target, target);
+        *vpath = saved_ch;
+        
+        if (file_exists (new_target)) {
+            return new_target;
+        }
+        
+        free (new_target);
+        
+        if (!(vpath = strchr (vpath, ';'))) {
+            break;
+        }
+        
+        vpath++;
+    
+    }
+    
+    return 0;
+
+}
+
+int rule_search_and_build (char *name) {
+
+    struct rule *r;
+    struct suffix_rule *s;
+    
+    char *suffix;
+    char *new_name;
+    
+    if ((r = rule_find (name))) {
+        return rule_use (r, name);
+    }
+    
+    if ((suffix = strrchr (name, '.'))) {
+    
+        char *duplicate_name = xstrdup (name);
+        variable_change ("@", xstrdup (duplicate_name), VAR_ORIGIN_AUTOMATIC);
+        
+        for (s = suffix_rules; s; s = s->next) {
+        
+            if (s->second && strcmp (suffix, s->second) == 0) {
+            
+                char *prereq_name, *p;
+                char *new_name;
+                
+                prereq_name = xmalloc (strlen (duplicate_name) + strlen (s->first) + 1);
+                sprintf (prereq_name, "%s", duplicate_name);
+                
+                if ((p = strrchr (prereq_name, '.'))) {
+                    *p = '\0';
+                }
+                
+                strcat (prereq_name, s->first);
+                
+                if (!(new_name = find_target (prereq_name))) {
+                
+                    free (prereq_name);
+                    continue;
+                
+                }
+                
+                if (prereq_name == new_name) {
+                
+                    free (prereq_name);
+                    break;
+                
+                }
+                
+                free (prereq_name);
+                
+                if (strlen (s->first) < strlen (s->second)) {
+                    new_name = xrealloc (new_name, strlen (new_name) + strlen (s->second) - strlen (s->first) + 1);
+                }
+                
+                if ((p = strrchr (new_name, '.'))) {
+                    *p = '\0';
+                }
+                
+                strcat (new_name, s->second);
+                
+                free (duplicate_name);
+                duplicate_name = new_name;
+                
+                break;
+            
+            }
+        
+        }
+        
+        if (s) {
+        
+            int ret = suffix_rule_use (s, duplicate_name);
+            
+            free (duplicate_name);
+            return ret;
+        
+        }
+        
+        free (duplicate_name);
+    
+    } else {
+    
+        variable_change ("@", xstrdup (name), VAR_ORIGIN_AUTOMATIC);
+        
+        for (s = suffix_rules; s; s = s->next) {
+        
+            if (!s->second) {
+            
+                char *prereq_name, *p;
+                char *new_name;
+                
+                prereq_name = xmalloc (strlen (name) + strlen (s->first) + 1);
+                strcpy (prereq_name, name);
+                strcat (prereq_name, s->first);
+                
+                if (!(new_name = find_target (prereq_name))) {
+                
+                    free (prereq_name);
+                    continue;
+                
+                }
+                
+                if (prereq_name != new_name) {
+                    free (prereq_name);
+                }
+                
+                p = new_name + strlen (new_name) - strlen (s->first);
+                *p = '\0';
+                
+                single_suffix_rule_use (s, new_name);
+                free (new_name);
+                
+                return 0;
+            
+            }
+        
+        }
+    
+    }
+    
+    new_name = find_target (name);
+    
+    if (new_name != name) {
+        free (new_name);
+    }
+    
+    if (!new_name) {
+    
+        fprintf (stderr, "%s: *** No rule to make target '%s'. Stop.\n", program_name, name);
+        return 1;
+    
+    }
+    
+    return 0;
+
+}
+
+int main (int argc, char **argv) {
+
+    int ret;
+    unsigned long i;
+    
+    if (argc && *argv) {
+    
+        char *p;
+        program_name = *argv;
+        
+        if ((p = strrchr (program_name, '/')) || (p = strrchr (program_name, '\\'))) {
+            program_name = (p + 1);
+        }
+    
+    }
+    
+    variables_init ();
+    rules_init ();
+    
+    default_goal_var = variable_add (xstrdup (".DEFAULT_GOAL"), xstrdup (""), VAR_ORIGIN_FILE);
+    
+    variable_add (xstrdup ("OS"), xstrdup (os_name), VAR_ORIGIN_FILE);
+    variable_add (xstrdup ("MAKE"), xstrdup (argv[0]), VAR_ORIGIN_FILE);
+    
+    state = xmalloc (sizeof (*state));
+    parse_args (argv, argc);
+    
+    if (state->nb_directories > 0) {
+    
+        char *arg;
+        size_t len = 0;
+        
+        char *cwd = get_current_directory ();
+        unsigned long i;
+        
+        state->path = xmalloc (strlen (cwd) + 2);
+        len = sprintf (state->path, "%s/", cwd);
+        
+        for (i = 0; i < state->nb_directories; i++) {
+        
+            arg = state->directories[i];
+            
+#if     defined (_WIN32)
+            if (strlen (arg) < 3 || !(isalpha ((int) arg[0]) && arg[1] == ':' && (arg[2] == '/' || arg[2] == '\\'))) {
+#else
+            if (arg[0] != '/') {
+#endif
+            
+                state->path = xrealloc (state->path, len + strlen (arg) + 2);
+                
+                len += sprintf (state->path + len, "%s/", arg);
+                state->path[len] = '\0';
+            
+            } else {
+            
+                free (state->path);
+                
+                state->path = xmalloc (strlen (arg) + 1);
+                len = sprintf (state->path, "%s/", arg);
+            
+            }
+        
+        }
+        
+        if (!directory_exists (state->path)) {
+        
+            fprintf (stderr, "%s: *** '%s': No such directory. Stop.\n", program_name, state->path);
+            return EXIT_FAILURE;
+        
+        }
+        
+        if (!state->no_print) {
+            fprintf (stderr, "%s: Entering directory '%s'\n", program_name, state->path);
+        }
+        
+        if (!change_directory (state->path)) {
+        
+            fprintf (stderr, "%s: *** Failed to enter '%s'. Stop.\n", program_name, state->path);
+            return EXIT_FAILURE;
+        
+        }
+    
+    }
+    
+    {
+    
+        char *name = "CURDIR", *cwd, *path;
+        size_t len;
+        
+        cwd = get_current_directory ();
+        len = strlen (name) + 4 + strlen (cwd);
+        
+        path = xmalloc (len + 1);
+        sprintf (path, "%s ?= %s", name, cwd);
+        
+        parse_var_line (path, VAR_ORIGIN_FILE);
+        free (path);
+        
+        /*variable_add (xstrdup ("CURDIR"), xstrdup (get_current_directory ()), VAR_ORIGIN_FILE);*/
+    
+    }
+    
+    if (state->nb_makefiles > 0) {
+    
+        char *path;
+        unsigned long i;
+        
+        for (i = 0; i < state->nb_makefiles; i++) {
+        
+            path = state->makefiles[i];
+            
+            if ((ret = read_makefile (path))) {
+            
+                fprintf (stderr, "%s: *** Failed to read '%s'. Stop.\n", program_name, path);
+                goto out;
+            
+            }
+        
+        }
+    
+    }
+    
+    if (state->nb_goals == 0) {
+    
+        if (default_goal_var->value[0] == '\0') {
+        
+            fprintf (stderr, "%s: *** No targets. Stop.\n", program_name);
+            
+            ret = EXIT_FAILURE;
+            goto out;
+        
+        }
+        
+        dynarray_add (&state->goals, &state->nb_goals, xstrdup (default_goal_var->value));
+    
+    }
+    
+    for (i = 0; i < state->nb_goals; i++) {
+    
+        if ((ret = rule_search_and_build (state->goals[i]))) {
+            goto out;
+        }
+    
+    }
+    
+    ret = EXIT_SUCCESS;
+    
+out:
+    
+    if (state->nb_directories > 0 && !state->no_print) {
+        fprintf (stderr, "%s: Leaving directory '%s'\n", program_name, state->path);
+    }
+    
+    return ret;
+
+}