Ejemplo n.º 1
0
// same as SameValue, except in its treatment of +/- 0
EJSBool
SameValueZero(ejsval x, ejsval y)
{
    // 1. ReturnIfAbrupt(x).
    // 2. ReturnIfAbrupt(y).

    // 3. If Type(x) is different from Type(y), return false.
    if (EJSVAL_TO_TAG(x) != EJSVAL_TO_TAG(y)) return EJS_FALSE;

    // 4. If Type(x) is Undefined, return true.
    if (EJSVAL_IS_UNDEFINED(x)) return EJS_TRUE;

    // 5. If Type(x) is Null, return true.
    if (EJSVAL_IS_NULL(x)) return EJS_TRUE;

    // 6. If Type(x) is Number, then
    if (EJSVAL_IS_NUMBER(x)) {
        //    a. If x is NaN and y is NaN, return true.
        if (isnan(EJSVAL_TO_NUMBER(x)) && isnan(EJSVAL_TO_NUMBER(y))) return EJS_TRUE;
        //    b. If x is +0 and y is -0, return true.
        if (EJSVAL_TO_NUMBER(x) == 0.0 && EJSDOUBLE_IS_NEGZERO(EJSVAL_TO_NUMBER(y))) return EJS_TRUE;
        //    c. If x is -0 and y is +0, return tryue.
        if (EJSDOUBLE_IS_NEGZERO(EJSVAL_TO_NUMBER(x)) == 0.0 && EJSVAL_TO_NUMBER(y) == 0) return EJS_TRUE;
        //    d. If x is the same Number value as y, return true.
        if (EJSVAL_TO_NUMBER(x) == EJSVAL_TO_NUMBER(y)) return EJS_TRUE;
        //    e. Return false.
        return EJS_FALSE;
    }
    // 7. If Type(x) is String, then
    if (EJSVAL_IS_STRING(x)) {
        //    a. If x and y are exactly the same sequence of code units (same length and same code units in corresponding positions) return true;
        //       otherwise, return false.
        if (EJSVAL_TO_STRLEN(x) != EJSVAL_TO_STRLEN(y)) return EJS_FALSE;

        // XXX there is doubtless a more efficient way to compare two ropes, but we convert but to flat strings for now.
        return ucs2_strcmp (EJSVAL_TO_FLAT_STRING(x), EJSVAL_TO_FLAT_STRING(y)) ? EJS_FALSE : EJS_TRUE;
    }
    // 8. If Type(x) is Boolean, then
    if (EJSVAL_IS_BOOLEAN(x)) {
        //    a. If x and y are both true or both false, then return true; otherwise, return false.
        return EJSVAL_TO_BOOLEAN(x) == EJSVAL_TO_BOOLEAN(y) ? EJS_TRUE : EJS_FALSE;
    }
    // 9. If Type(x) is Symbol, then
    if (EJSVAL_IS_SYMBOL(x)) {
        //    a. If x and y are both the same Symbol value, then return true; otherwise, return false.
        EJS_NOT_IMPLEMENTED();
    }
    // 10. Return true if x and y are the same Object value. Otherwise, return false.
    return EJSVAL_EQ(x, y);
}
Ejemplo n.º 2
0
ejsval
_ejs_stream_write (ejsval env, ejsval _this, uint32_t argc, ejsval* args)
{
    ejsval to_write = ToString(args[0]);
    ejsval internal_fd = _ejs_object_getprop_utf8 (_this, "%internal_fd");
    int fd = ToInteger(internal_fd);

    int remaining = EJSVAL_TO_STRLEN(to_write);
    int offset = 0;
    char *buf = ucs2_to_utf8(EJSVAL_TO_FLAT_STRING(to_write));
    
    do {
        int num_written = write (fd, buf + offset, remaining);
        if (num_written == -1) {
            if (errno == EINTR)
                continue;
            perror ("write");
            free (buf);
            return _ejs_false;
        }
        remaining -= num_written;
        offset += num_written;
    } while (remaining > 0);

    free (buf);
    return _ejs_true;
}
Ejemplo n.º 3
0
static ejsval
_ejs_arguments_specop_get (ejsval obj, ejsval propertyName, ejsval receiver)
{
    EJSArguments* arguments = EJSVAL_TO_ARGUMENTS(obj);

    // check if propertyName is an integer, or a string that we can convert to an int
    EJSBool is_index = EJS_FALSE;
    ejsval idx_val = ToNumber(propertyName);
    int idx;
    if (EJSVAL_IS_NUMBER(idx_val)) {
        double n = EJSVAL_TO_NUMBER(idx_val);
        if (floor(n) == n) {
            idx = (int)n;
            is_index = EJS_TRUE;
        }
    }

    if (is_index) {
        if (idx < 0 || idx > arguments->argc) {
            printf ("getprop(%d) on an arguments, returning undefined\n", idx);
            return _ejs_undefined;
        }
        return arguments->args[idx];
    }

    // we also handle the length getter here
    if (EJSVAL_IS_STRING(propertyName) && !ucs2_strcmp (_ejs_ucs2_length, EJSVAL_TO_FLAT_STRING(propertyName))) {
        return NUMBER_TO_EJSVAL(arguments->argc);
    }

    // otherwise we fallback to the object implementation
    return _ejs_Object_specops.Get (obj, propertyName, receiver);
}
Ejemplo n.º 4
0
static ejsval
_ejs_arraybuffer_specop_get (ejsval obj, ejsval propertyName, ejsval receiver)
{
    // check if propertyName is an integer, or a string that we can convert to an int
    EJSBool is_index = EJS_FALSE;
    int idx = 0;
    if (EJSVAL_IS_NUMBER(propertyName)) {
        double n = EJSVAL_TO_NUMBER(propertyName);
        if (floor(n) == n) {
            idx = (int)n;
            is_index = EJS_TRUE;
        }
    }

    if (is_index) {
        if (idx < 0 || idx > EJS_ARRAY_LEN(obj)) {
            printf ("getprop(%d) on an array, returning undefined\n", idx);
            return _ejs_undefined;
        }
        return EJS_DENSE_ARRAY_ELEMENTS(obj)[idx];
    }

    // we also handle the length getter here
    if (EJSVAL_IS_STRING(propertyName) && !ucs2_strcmp (_ejs_ucs2_byteLength, EJSVAL_TO_FLAT_STRING(propertyName))) {
        return NUMBER_TO_EJSVAL (EJS_ARRAY_BUFFER_BYTE_LEN(obj));
    }

    // otherwise we fallback to the object implementation
    return _ejs_Object_specops.get (obj, propertyName, receiver);
}
Ejemplo n.º 5
0
/* 15.12.2 */
static ejsval
_ejs_JSON_parse (ejsval env, ejsval _this, uint32_t argc, ejsval *args)
{
    ejsval text = _ejs_undefined;
    ejsval reviver = _ejs_undefined;

    if (argc > 0) text = args[0];
    if (argc > 1) reviver = args[1];


    /* 1. Let JText be ToString(text). */
    ejsval jtext = ToString(text);

    /* 2. Parse JText using the grammars in 15.12.1. Throw a SyntaxError exception if JText did not conform to the 
       JSON grammar for the goal symbol JSONText.  */
    char *flattened_jtext =  ucs2_to_utf8(EJSVAL_TO_FLAT_STRING(jtext));

    /* 3. Let unfiltered be the result of parsing and evaluating JText as if it was the source text of an ECMAScript 
       Program but using JSONString in place of StringLiteral. Note that since JText conforms to the JSON 
       grammar this result will be either a primitive value or an object that is defined by either an ArrayLiteral or 
       an ObjectLiteral. */
    JSON_Value* root_val = json_parse_string(flattened_jtext);

    free(flattened_jtext);

    if (root_val == NULL) {
        printf ("SyntaxError\n");
        EJS_NOT_IMPLEMENTED();
    }

    ejsval unfiltered;
    if (!json_value_to_ejsval(root_val, &unfiltered)) {
        json_value_free (root_val);
        /* unfiltered here is an exception */
        EJS_NOT_IMPLEMENTED();
    }

    json_value_free (root_val);

    /* 4. If IsCallable(reviver) is true, then */
    if (EJSVAL_IS_CALLABLE(reviver)) {
        printf ("no reviver support in JSON.parse yet\n");
        EJS_NOT_IMPLEMENTED();
        /*    a. Let root be a new object created as if by the expression new Object(), where Object is the
              standard built-in constructor with that name. */
        /*    b. Call the [[DefineOwnProperty]] internal method of root with the empty String, the 
              PropertyDescriptor {[[Value]]: unfiltered, [[Writable]]: true, [[Enumerable]]: true, 
              [[Configurable]]: true}, and false as arguments. */
        /*    c. Return the result of calling the abstract operation Walk, passing root and the empty String. The 
              abstract operation Walk is described below. */
    }
    /* 5. Else */
    else {
        /*    a. Return unfiltered. */
        return unfiltered;
    }
}
Ejemplo n.º 6
0
static ejsval
_ejs_path_basename (ejsval env, ejsval _this, uint32_t argc, ejsval* args)
{
    ejsval path = args[0];
    // FIXME node's implementation allows a second arg to strip the extension, but the compiler doesn't use it.
    char *utf8_path = ucs2_to_utf8(EJSVAL_TO_FLAT_STRING(path));
    ejsval rv = _ejs_string_new_utf8(basename (utf8_path));
    free (utf8_path);
    return rv;
}
Ejemplo n.º 7
0
static ejsval
_ejs_fs_readFileSync (ejsval env, ejsval _this, uint32_t argc, ejsval* args)
{
    // FIXME we currently ignore the encoding and just slam the entire thing into a buffer and return a utf8 string...
    char* utf8_path = ucs2_to_utf8(EJSVAL_TO_FLAT_STRING(args[0]));

    int fd = open (utf8_path, O_RDONLY);
    if (fd == -1) {
        char buf[256];
        snprintf (buf, sizeof(buf), "%s: `%s`", strerror(errno), utf8_path);
        free(utf8_path);
        _ejs_throw_nativeerror_utf8 (EJS_ERROR, buf);
    }

    struct stat fd_stat;

    int stat_rv = fstat (fd, &fd_stat);
    if (stat_rv == -1) {
        char buf[256];
        snprintf (buf, sizeof(buf), "%s: `%s`", strerror(errno), utf8_path);
        free(utf8_path);
        close(fd);
        _ejs_throw_nativeerror_utf8 (EJS_ERROR, buf);
    }

    int amount_to_read = fd_stat.st_size;
    int amount_read = 0;
    char *buf = (char*)calloc (1, amount_to_read+1);
    do {
        int c = read(fd, buf + amount_read, amount_to_read);
        if (c == -1) {
            if (errno == EINTR)
                continue;
            else {
                char msg[256];
                snprintf (msg, sizeof(msg), "%s: `%s`", strerror(errno), utf8_path);
                free(utf8_path);
                close(fd);
                free (buf);
                _ejs_throw_nativeerror_utf8 (EJS_ERROR, msg);
            }
        }
        else {
            amount_to_read -= c;
            amount_read += c;
        }
    } while (amount_to_read > 0);

    free(utf8_path);

    ejsval rv = _ejs_string_new_utf8_len(buf, amount_read);
    free(buf);
    return rv;
}
Ejemplo n.º 8
0
static ejsval
_ejs_child_process_spawn (ejsval env, ejsval _this, uint32_t argc, ejsval* args)
{
    char* argv0 = ucs2_to_utf8(EJSVAL_TO_FLAT_STRING(args[0]));
    EJSArray* argv_rest = (EJSArray*)EJSVAL_TO_OBJECT(args[1]);

    char **argv = (char**)calloc(sizeof(char*), EJSARRAY_LEN(argv_rest) + 2);
    argv[0] = argv0;
    for (uint32_t i = 0; i < EJSARRAY_LEN(argv_rest); i ++)
        argv[1+i] = ucs2_to_utf8(EJSVAL_TO_FLAT_STRING(ToString(EJSDENSEARRAY_ELEMENTS(argv_rest)[i])));

    pid_t pid;
    switch (pid = fork()) {
    case -1: /* error */
        perror("fork");
        printf ("we should totally throw an exception here\n");
        break;
    case 0:  /* child */
        execvp (argv0, argv);
        perror("execv");
        EJS_NOT_REACHED();
        break;
    default: /* parent */ {
        int stat;
        int wait_rv;
        do {
            wait_rv = waitpid(pid, &stat, 0);
        } while (wait_rv == -1 && errno == EINTR);

        if (wait_rv != pid) {
            perror ("waitpid");
            printf ("we should totally throw an exception here\n");
        }
        break;
    }
    }
    for (uint32_t i = 0; i < EJSARRAY_LEN(argv_rest)+1; i ++)
        free (argv[i]);
    free (argv);
    return _ejs_undefined;
}
Ejemplo n.º 9
0
void
_ejs_throw_nativeerror (EJSNativeErrorType error_type, ejsval message)
{
    ejsval exc = _ejs_nativeerror_new (error_type, message);

    char *message_utf8 = ucs2_to_utf8(EJSVAL_TO_FLAT_STRING(message));
    _ejs_log ("throwing exception with message %s\n", message_utf8);
    free (message_utf8);

    _ejs_throw (exc);
    EJS_NOT_REACHED();
}
Ejemplo n.º 10
0
static EJS_NATIVE_FUNC(_ejs_Process_chdir) {
    ejsval dir = _ejs_undefined;
    if (argc > 0)
        dir = args[0];

    if (!EJSVAL_IS_STRING(dir))
        _ejs_throw_nativeerror_utf8 (EJS_TYPE_ERROR, "chdir passed non-string");
        
    char *dir_utf8 = ucs2_to_utf8(EJSVAL_TO_FLAT_STRING(dir));
    chdir(dir_utf8);
    free(dir_utf8);

    return _ejs_undefined;
}
Ejemplo n.º 11
0
ejsval
_ejs_fs_createWriteStream (ejsval env, ejsval _this, uint32_t argc, ejsval* args)
{
    char *utf8_path = ucs2_to_utf8(EJSVAL_TO_FLAT_STRING(args[0]));

    int fd = open (utf8_path, O_CREAT | O_TRUNC | O_WRONLY, 0777);
    free (utf8_path);
    if (fd == -1) {
        perror ("open");
        printf ("we should totally throw an exception here\n");
        return _ejs_undefined;
    }

    return _ejs_wrapFdWithStream(fd);  
}
Ejemplo n.º 12
0
static ejsval
_ejs_Process_chdir (ejsval env, ejsval _this, uint32_t argc, ejsval* args)
{
    ejsval dir = _ejs_undefined;
    if (argc > 0)
        dir = args[0];

    if (!EJSVAL_IS_STRING(dir))
        _ejs_throw_nativeerror_utf8 (EJS_TYPE_ERROR, "chdir passed non-string");
        
    char *dir_utf8 = ucs2_to_utf8(EJSVAL_TO_FLAT_STRING(dir));
    chdir(dir_utf8);
    free(dir_utf8);

    return _ejs_undefined;
}
Ejemplo n.º 13
0
// ECMA262 15.3.4.2
static ejsval
_ejs_Function_prototype_toString (ejsval env, ejsval _this, uint32_t argc, ejsval *args)
{
    char terrible_fixed_buffer[256];

    if (!EJSVAL_IS_FUNCTION(_this))
        _ejs_throw_nativeerror_utf8 (EJS_TYPE_ERROR, "Function.prototype.toString is not generic.");

    ejsval func_name = _ejs_object_getprop (_this, _ejs_atom_name);

    char *utf8_funcname = ucs2_to_utf8(EJSVAL_TO_FLAT_STRING(func_name));
    
    snprintf (terrible_fixed_buffer, sizeof (terrible_fixed_buffer), "function %s() {}", utf8_funcname);

    free (utf8_funcname);

    return _ejs_string_new_utf8(terrible_fixed_buffer);
}
Ejemplo n.º 14
0
static ejsval
_ejs_path_resolve (ejsval env, ejsval _this, uint32_t argc, ejsval* args)
{
    char** paths_utf8 = (char**)calloc(argc + 1, sizeof(char*));
    int num_paths = 0;

    char cwd[MAXPATHLEN];
    getcwd(cwd, MAXPATHLEN);

    paths_utf8[num_paths++] = strdup(cwd);

    for (int i = 0; i < argc; i ++) {
        ejsval arg = args[i];

        if (!EJSVAL_IS_STRING(arg)) {
            for (int j = 0; j < num_paths; j ++) free(paths_utf8[j]);
            free (paths_utf8);
            _ejs_throw_nativeerror_utf8 (EJS_TYPE_ERROR, "Arguments to path.resolve must be strings");
        }

        paths_utf8[num_paths++] = ucs2_to_utf8(EJSVAL_TO_FLAT_STRING(arg));
    }

    int start_path;
    for (start_path = num_paths-1; start_path >= 0; start_path --) {
        if (paths_utf8[start_path][0] == '/')
            break;
    }
    // at this point paths_utf8[start_path] is our "root" for
    // resolving.  it is either the right-most absolute path in the
    // argument list, or $cwd if there wasn't an absolute path in the
    // args.

    char* resolved = resolvev(&paths_utf8[start_path], num_paths - start_path);

    ejsval rv = _ejs_string_new_utf8(resolved);

    free (resolved);
    for (int j = 0; j < num_paths; j ++) free(paths_utf8[j]);
    free (paths_utf8);

    return rv;
}
Ejemplo n.º 15
0
static ejsval
_ejs_fs_readFileSync (ejsval env, ejsval _this, uint32_t argc, ejsval* args)
{
    // FIXME we currently ignore the encoding and just slam the entire thing into a buffer and return a utf8 string...
    char* utf8_path = ucs2_to_utf8(EJSVAL_TO_FLAT_STRING(args[0]));

    int fd = open (utf8_path, O_RDONLY);
    free(utf8_path);

    struct stat fd_stat;

    fstat (fd, &fd_stat);

    char *buf = (char*)malloc (fd_stat.st_size);
    read(fd, buf, fd_stat.st_size);
    close(fd);

    ejsval rv = _ejs_string_new_utf8_len(buf, fd_stat.st_size);
    free(buf);
    return rv;
}
Ejemplo n.º 16
0
ejsval
_ejs_fs_createWriteStream (ejsval env, ejsval _this, uint32_t argc, ejsval* args)
{
    ejsval stream = _ejs_object_create(_ejs_null);

    EJS_INSTALL_FUNCTION (stream, "write", _ejs_stream_write);
    EJS_INSTALL_FUNCTION (stream, "end", _ejs_stream_end);

    char *utf8_path = ucs2_to_utf8(EJSVAL_TO_FLAT_STRING(args[0]));

    int fd = open (utf8_path, O_CREAT | O_TRUNC | O_WRONLY, 0777);
    free (utf8_path);
    if (fd == -1) {
        perror ("open");
        printf ("we should totally throw an exception here\n");
        return _ejs_undefined;
    }

    _ejs_object_setprop_utf8 (stream, "%internal_fd", NUMBER_TO_EJSVAL(fd));

    return stream;
}
Ejemplo n.º 17
0
ejsval
_ejs_module_get (ejsval arg)
{
    char* arg_utf8 = ucs2_to_utf8(EJSVAL_TO_FLAT_STRING(arg));

    ejsval module EJSVAL_ALIGNMENT;
    if (require_builtin_module (arg_utf8, &module)) {
        free (arg_utf8);
        return module;
    }
    if (require_external_module (arg_utf8, &module)) {
        free (arg_utf8);
        return module;
    }
    if (require_user_module (arg_utf8, &module)) {
        free (arg_utf8);
        return module;
    }
    _ejs_log ("require('%s') failed: module not included in build.\n", arg_utf8);
    free (arg_utf8);
    return _ejs_null;
}
Ejemplo n.º 18
0
ejsval ToNumber(ejsval exp)
{
    if (EJSVAL_IS_NUMBER(exp))
        return exp;
    else if (EJSVAL_IS_BOOLEAN(exp))
        return EJSVAL_TO_BOOLEAN(exp) ? _ejs_one : _ejs_zero;
    else if (EJSVAL_IS_STRING(exp)) {
        char* num_utf8 = ucs2_to_utf8(EJSVAL_TO_FLAT_STRING(exp));
        char *endptr;
        double d = strtod(num_utf8, &endptr);
        if (*endptr != '\0')
            return _ejs_nan;
        ejsval rv = NUMBER_TO_EJSVAL(d); // XXX NaN
        free (num_utf8);
        return rv;
    }
    else if (EJSVAL_IS_UNDEFINED(exp))
        return _ejs_nan;
    else if (EJSVAL_IS_OBJECT(exp)) {
        if (EJSVAL_IS_DATE(exp)) {
            return NUMBER_TO_EJSVAL(_ejs_date_get_time ((EJSDate*)EJSVAL_TO_OBJECT(exp)));
        }
        else if (EJSVAL_IS_ARRAY(exp)) {
            int len = EJS_ARRAY_LEN(exp);
            if (len == 0) return _ejs_zero;
            else if (len > 1) return _ejs_nan;
            else {
                // XXX we need to support sparse arrays here too
                EJS_ASSERT (EJSVAL_IS_DENSE_ARRAY(exp));
                return ToNumber(EJS_DENSE_ARRAY_ELEMENTS(exp)[0]);
            }
        }
        else
            return _ejs_nan;
    }
    else
        EJS_NOT_IMPLEMENTED();
}
Ejemplo n.º 19
0
static ejsval
Encode (ejsval string, ejsval unescaped)
{
    EJSPrimString *stringObj = EJSVAL_TO_STRING(string);
    jschar *unescapedStr = EJSVAL_TO_FLAT_STRING(unescaped);

    /* 1. Let strLen be the number of code units in string. */
    int32_t strLen = stringObj->length;

    /* 2. Let R be the empty String. */
    ejsval R = _ejs_atom_empty;

    /* 3. Let k be 0. */
    int32_t k = 0;

    /* 4. Repeat. */
    for (;;) {
        /* a. If k equals strLen, return R. */
        if (k == strLen)
            return R;

        /* b. Let C be the code unit at index k within string. */
        jschar C = _ejs_string_ucs2_at (stringObj, k);
        jschar C_arr [2] = { C, 0 };

        /* c. If C is in unescapedSet, then */
        jschar *p = ucs2_strstr (unescapedStr, C_arr);
        if (p) {
            /* i. Let S be a String containing only the code unit C. */
            ejsval S = _ejs_string_new_substring (string, k, 1);

            /* ii. Let R be a new String value computed by concatenating the previous value of R and S. */
            R = _ejs_string_concat (R, S);
        }
        /* d. Else C is not in unescapedSet, */
        else {
            /* i. If the code unit value of C is not less than 0xDC00 and not greater than 0xDFFF, throw a URIError exception. */
            if (C >= 0xDC00 && C <= 0xDFFF)
                _ejs_throw_nativeerror_utf8 (EJS_URI_ERROR, "URI malformed");

            /* ii. If the code unit value of C is less than 0xD800 or greater than 0xDBFF, then Let V be the code unit value of C. */
            jschar V;
            if (C < 0xD800 || C > 0xDBFF)
                V = C;
            /* iii. Else, */
            else {
                /* 1. Increase k by 1. */
                k++;

                /* 2. If k equals strLen, throw a URIError exception. */
                if (k == strLen)
                    _ejs_throw_nativeerror_utf8 (EJS_URI_ERROR, "URI malformed");

                /* 3. Let kChar be the code unit value of the code unit at index k within string. */
                jschar kChar = _ejs_string_ucs2_at (stringObj, k);

                /* 4. If kChar is less than 0xDC00 or greater than 0xDFFF, throw a URIError exception. */
                if (kChar < 0xDC00 || kChar > 0xDFFF)
                    _ejs_throw_nativeerror_utf8 (EJS_URI_ERROR, "URI malformed");

                /* 5. Let V be (((the code unit value of C) – 0xD800) × 0x400 + (kChar – 0xDC00) + 0x10000). */
                V = (C - 0xD800) * 0x400 + (kChar - 0xDC00) + 0x10000;
            }

            /* iv. Let Octets be the array of octets resulting by applying the UTF-8 transformation to V, and let L be the array size. */
            char octets[4];
            int32_t L = ucs2_to_utf8_char (V, octets);

            /* v. Let j be 0. */
            int32_t j = 0;

            /* vi. Repeat, while j < L. */
            while (j < L) {
                /* 1.  Let jOctet be the value at index j within Octets. */
                char jOctet = octets [j];

                /* 2. Let S be a String containing three code units “%XY” where XY are two uppercase hexadecimal
                 * digits encoding the value of jOctet. */
                char buff[4];
                sprintf(buff, "%%%X", jOctet);
                ejsval S = _ejs_string_new_utf8 (buff);

                /* 3. Let R be a new String value computed by concatenating the previous value of R and S. */
                R = _ejs_string_concat (R, S);

                /* 4. Increase j by 1. */
                j++;
            }
        }

        /* e. Increase k by 1. */
        k++;
    }
}
Ejemplo n.º 20
0
static ejsval
_ejs_path_relative (ejsval env, ejsval _this, uint32_t argc, ejsval* args)
{
    ejsval from = _ejs_undefined;
    ejsval to   = _ejs_undefined;

    if (argc > 0) from = args[0];
    if (argc > 1) to   = args[1];

    if (!EJSVAL_IS_STRING(from) || !EJSVAL_IS_STRING(to))
        _ejs_throw_nativeerror_utf8 (EJS_TYPE_ERROR, "Arguments to path.relative must be strings");

    char *from_utf8 = ucs2_to_utf8(EJSVAL_TO_FLAT_STRING(from));
    char *to_utf8   = ucs2_to_utf8(EJSVAL_TO_FLAT_STRING(to));

    if (from_utf8[0] != '/') from_utf8 = make_absolute(from_utf8);
    if (to_utf8[0]   != '/') to_utf8   = make_absolute(to_utf8);

    char* p = to_utf8 + strlen(to_utf8) - 1;
    int up = 0;
    EJSBool seen_slash = EJS_FALSE;

    while (p != to_utf8) {
        if (*p == '/') {
            if (seen_slash) continue; // skip adjacent slashes
            seen_slash = EJS_TRUE;
            char* prefix = strndup(to_utf8, p - to_utf8);
            if (!strcmp(from_utf8, prefix)) {
                up = -1;
                free (prefix);
                goto done;
            }
            if (strstr(from_utf8, prefix) == from_utf8) {
                free (prefix);
                goto done;
            }
            free (prefix);
            up ++;
        }
        else {
            seen_slash = EJS_FALSE;
        }
        p--;
    }
    // we made it all the way to the end, fall through to building up our string

 done:
    {
        ejsval dotdotslash = _ejs_string_new_utf8("../");
        ejsval rv = _ejs_string_new_utf8(p+1);
        while (up >= 0) {
            rv = _ejs_string_concat(dotdotslash, rv);
            up--;
        }

        free (from_utf8);
        free (to_utf8);

        return rv;
    }
}