summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--README.md129
-rw-r--r--compiler.c53
-rw-r--r--lexer.c1
-rw-r--r--lexer.h1
-rw-r--r--lib.c30
-rw-r--r--value.c16
-rw-r--r--value.h1
-rw-r--r--vm.c29
-rw-r--r--vm.h3
9 files changed, 173 insertions, 90 deletions
diff --git a/README.md b/README.md
index 210c75c..55ac9c3 100644
--- a/README.md
+++ b/README.md
@@ -510,6 +510,22 @@ The result of assignment expressions is the assigned value.
%}
```
+#### 5.6. Miscellaneous operators
+
+Besides the operators described so far, ucode script also supports a `delete`
+operator which removes a property from an object value.
+
+```javascript
+{%
+ a = { test: true };
+
+ delete a.test; // true
+ delete a.notexisting; // false
+
+ print(a); // { }
+%}
+```
+
### 6. Functions
Ucode scripts may call a number of builtin functions to manipulate values or
@@ -549,30 +565,25 @@ chr(-1, 300); // string consisting of an `0x0` and a `0xff` byte
Return the cosine of x, where x is given in radians.
-#### 6.5. `delete(obj, key1, ...)`
-
-Delete the given key(s) from the object passed as first argument. Returns the
-corresponding value of the last removed key, if any.
-
-#### 6.6. `die(msg)`
+#### 6.5. `die(msg)`
Raise an exception with the given message and abort execution.
-#### 6.7. `exists(obj, key)`
+#### 6.6. `exists(obj, key)`
Return `true` if the given key is present within the object passed as first
argument, otherwise `false`.
-#### 6.8. `exit(n)`
+#### 6.7. `exit(n)`
Terminate the interpreter with the given exit code.
-#### 6.9. `exp(n)`
+#### 6.8. `exp(n)`
Return the value of e (the base of natural logarithms) raised to the power
of n.
-#### 6.10. `filter(arr, fn)`
+#### 6.9. `filter(arr, fn)`
Filter the array passed as first argument by invoking the function specified
in the second argument for each array item.
@@ -598,15 +609,15 @@ a = filter(["foo", 1, true, null, 2.2], function(v) {
// a = [1, 2.2]
```
-#### 6.11. `getenv(name)`
+#### 6.10. `getenv(name)`
Return the value of the given environment variable.
-#### 6.12. `hex(x)`
+#### 6.11. `hex(x)`
Convert the given hexadecimal string into a number.
-#### 6.13. `index(arr_or_str, needle)`
+#### 6.12. `index(arr_or_str, needle)`
Find the given value passed as second argument within the array or string
specified in the first argument.
@@ -616,28 +627,28 @@ if the value was not found.
Returns `null` if the first argument was neither an array, nor a string.
-#### 6.14. `int(x)`
+#### 6.13. `int(x)`
Convert the given value to an integer. Returns `NaN` if the value is not
convertible.
-#### 6.15. `join(sep, arr)`
+#### 6.14. `join(sep, arr)`
Join the array passed as 2nd argument into a string, using the separator passed
in the first argument as glue. Returns `null` if the second argument is not an
array.
-#### 6.16. `keys(obj)`
+#### 6.15. `keys(obj)`
Return an array of all key names present in the passed object. Returns `null`
if the given argument is no object.
-#### 6.17. `lc(s)`
+#### 6.16. `lc(s)`
Convert the given string to lowercase and return the resulting string.
Returns `null` if the given argument could not be converted to a string.
-#### 6.18. `length(x)`
+#### 6.17. `length(x)`
Return the length of the given object, array or string. Returns `null` if
the given argument is neither an object, array, nor a string.
@@ -655,11 +666,11 @@ length(true) // null
length(10.0) // null
```
-#### 6.19. `log(x)`
+#### 6.18. `log(x)`
Return the natural logarithm of x.
-#### 6.20. `ltrim(s, c)`
+#### 6.19. `ltrim(s, c)`
Trim any of the specified characters in `c` from the start of `str`.
If the second argument is omitted, trims the characters, ` ` (space), `\t`,
@@ -670,7 +681,7 @@ ltrim(" foo \n") // "foo \n"
ltrim("--bar--", "-") // "bar--"
```
-#### 6.21. `map(arr, fn)`
+#### 6.20. `map(arr, fn)`
Transform the array passed as first argument by invoking the function specified
in the second argument for each array item.
@@ -694,7 +705,7 @@ a = map(["foo", 1, true, null, 2.2], type);
// a = ["string", "int", "bool", null, "double"]
```
-#### 6.22. `ord(s, ...)`
+#### 6.21. `ord(s, ...)`
Without further arguments, this function returns the byte value of the first
character in the given string.
@@ -713,35 +724,35 @@ ord("Abc", 2, 1, 0); // [ 99, 98, 65 ]
ord("Abc", 10, -10, "nan"); // [ null, null, null ]
```
-#### 6.23. `pop(arr)`
+#### 6.22. `pop(arr)`
Pops the last item from the given array and returns it. Returns `null` if the
array was empty or if a non-array argument was passed.
-#### 6.24. `print(x, ...)`
+#### 6.23. `print(x, ...)`
Print any of the given values to stdout. Arrays and objects are converted to
their JSON representation.
Returns the amount of bytes printed.
-#### 6.25. `push(arr, v1, ...)`
+#### 6.24. `push(arr, v1, ...)`
Push the given argument(s) to the given array. Returns the last pushed value.
-#### 6.26. `rand()`
+#### 6.25. `rand()`
Returns a random number. If `srand()` has not been called already, it is
automatically invoked passing the current time as seed.
-#### 6.27. `reverse(arr_or_str)`
+#### 6.26. `reverse(arr_or_str)`
If an array is passed, returns the array in reverse order. If a string is
passed, returns the string with the sequence of the characters reversed.
Returns `null` if neither an array nor a string were passed.
-#### 6.28. `rindex(arr_or_str, needle)`
+#### 6.27. `rindex(arr_or_str, needle)`
Find the given value passed as second argument within the array or string
specified in the first argument.
@@ -751,7 +762,7 @@ if the value was not found.
Returns `null` if the first argument was neither an array, nor a string.
-#### 6.29. `rtrim(str, c)`
+#### 6.28. `rtrim(str, c)`
Trim any of the specified characters in `c` from the end of `str`.
If the second argument is omitted, trims the characters, ` ` (space), `\t`,
@@ -762,16 +773,16 @@ rtrim(" foo \n") // " foo"
rtrim("--bar--", "-") // "--bar"
```
-#### 6.30. `shift(arr)`
+#### 6.29. `shift(arr)`
Pops the first item from the given array and returns it. Returns `null` if the
array was empty or if a non-array argument was passed.
-#### 6.31. `sin(x)`
+#### 6.30. `sin(x)`
Return the sine of x, where x is given in radians.
-#### 6.32. `sort(arr, fn)`
+#### 6.31. `sort(arr, fn)`
Sort the given array according to the given sort function. If no sort
function is provided, a default ascending sort order is applied.
@@ -783,7 +794,7 @@ sort(["Bean", "Orange", "Apple"], function(a, b) {
}) // ["Bean", "Apple", "Orange"]
```
-#### 6.33. `splice(arr, off, len, ...)`
+#### 6.32. `splice(arr, off, len, ...)`
Removes the elements designated by `off` and `len` from the given an array,
and replaces them with the additional arguments passed, if any. Returns the
@@ -794,7 +805,7 @@ If `off` is negative then it starts that far from the end of the array. If
removes the elements from `off` onward except for `-len` elements at the end of
the array. If both `off` and `len` are omitted, removes everything.
-#### 6.34. `split(str, sep)`
+#### 6.33. `split(str, sep)`
Split the given string using the separator passed as second argument and return
an array containing the resulting pieces.
@@ -807,15 +818,15 @@ split("foobar", "") // ["f", "o", "o", "b", "a", "r"]
split("foo,bar,baz", /[ao]/) // ["f", "", ",b", "r,b", "z"]
```
-#### 6.35. `sqrt(x)`
+#### 6.34. `sqrt(x)`
Return the nonnegative square root of x.
-#### 6.36. `srand(n)`
+#### 6.35. `srand(n)`
Seed the PRNG using the given number.
-#### 6.37. `substr(str, off, len)`
+#### 6.36. `substr(str, off, len)`
Extracts a substring out of `str` and returns it. First character is at offset
zero. If `off` is negative, starts that far back from the end of the string.
@@ -832,7 +843,7 @@ substr(s, -4); // tree
substr(s, -4, 2); // tr
```
-#### 6.38. `time()`
+#### 6.37. `time()`
Returns the current UNIX epoch.
@@ -840,7 +851,7 @@ Returns the current UNIX epoch.
time(); // 1598043054
```
-#### 6.39. `trim()`
+#### 6.38. `trim()`
Trim any of the specified characters in `c` from the start and end of `str`.
If the second argument is omitted, trims the characters, ` ` (space), `\t`,
@@ -851,14 +862,14 @@ ltrim(" foo \n") // "foo"
ltrim("--bar--", "-") // "bar"
```
-#### 6.40. `type(x)`
+#### 6.39. `type(x)`
Returns the type of the given value as string which might be one of
`"function"`, `"object"`, `"array"`, `"double"`, `"int"` or `"bool"`.
Returns `null` when no value or `null` is passed.
-#### 6.41. `uchr(n1, ...)`
+#### 6.40. `uchr(n1, ...)`
Converts each given numeric value to an utf8 escape sequence and returns the
resulting string. Invalid numeric values or values outside the range `0` ..
@@ -869,17 +880,17 @@ uchr(0x2600, 0x26C6, 0x2601); // "☀⛆☁"
uchr(-1, 0x20ffff, "foo"); // "���"
```
-#### 6.42. `uc(str)`
+#### 6.41. `uc(str)`
Converts the given string to uppercase and return the resulting string.
Returns `null` if the given argument could not be converted to a string.
-#### 6.43. `unshift(arr, v1, ...)`
+#### 6.42. `unshift(arr, v1, ...)`
Add the given values to the beginning of the array passed as first argument.
Returns the last value added to the array.
-#### 6.44. `values(obj)`
+#### 6.43. `values(obj)`
Returns an array containing all values of the given object. Returns `null` if
no object was passed.
@@ -888,7 +899,7 @@ no object was passed.
values({ foo: true, bar: false }); // [true, false]
```
-#### 6.45. `printf(fmt, ...)`
+#### 6.44. `printf(fmt, ...)`
Formats the given arguments according to the given format string and outputs the
result to stdout.
@@ -932,14 +943,14 @@ well.
%}
```
-#### 6.46. `sprintf(fmt, ...)`
+#### 6.45. `sprintf(fmt, ...)`
Formats the given arguments according to the given format string and returns the
resulting string.
See `printf()` for details.
-#### 6.47. `match(str, /pattern/)`
+#### 6.46. `match(str, /pattern/)`
Match the given string against the regular expression pattern specified as
second argument.
@@ -955,7 +966,7 @@ match("foobarbaz", /b.(.)/) // ["bar", "r"]
match("foobarbaz", /b.(.)/g) // [["bar", "r"], ["baz", "z"]]
```
-#### 6.48. `replace(str, /pattern/, replace)`
+#### 6.47. `replace(str, /pattern/, replace)`
Replace occurences of the specified pattern in the string passed as first
argument. The pattern value may be either a regular expression or a plain
@@ -988,7 +999,7 @@ replace("barfoobaz", /(.)(.)(.)/g, function(m, c1, c2, c3) {
}) // raboofzab
```
-#### 6.49. `json(str)`
+#### 6.48. `json(str)`
Parse the given string as JSON and return the resulting value. Throws an
exception on parse errors, trailing garbage or premature EOF.
@@ -998,7 +1009,7 @@ json('{"a":true, "b":123}') // { "a": true, "b": 123 }
json('[1,2,') // Throws exception
```
-#### 6.50. `include(path[, scope])`
+#### 6.49. `include(path[, scope])`
Evaluate and include the file at the given path and optionally override the
execution scope with the given scope object.
@@ -1041,14 +1052,14 @@ include("./untrusted.uc", proto({
}, {}))
```
-#### 6.51. `warn(x, ...)`
+#### 6.50. `warn(x, ...)`
Print any of the given values to stderr. Arrays and objects are converted to
their JSON representation.
Returns the amount of bytes printed.
-#### 6.52. `system(command, timeout)`
+#### 6.51. `system(command, timeout)`
Executes the given command, waits for completion and returns the resulting
exit code.
@@ -1078,7 +1089,7 @@ system(["/usr/bin/date", "+%s"]); // prints the UNIX timestamp to stdou
system("sleep 3 && echo 'Success'", 1000); // returns -9
```
-#### 6.53. `trace(level)`
+#### 6.52. `trace(level)`
Enables or disables VM opcode tracing. When invoked with a positive non-zero
level, opcode tracing is enabled and debug information is printed to stderr
@@ -1091,7 +1102,7 @@ implementation might provide different different verbosity levels or treat
the level argument as bit mask to enable or disable individual debug
elements.
-#### 6.54. `proto(val[, proto])`
+#### 6.53. `proto(val[, proto])`
Get or set the prototype of the array or object value `val`.
@@ -1104,23 +1115,23 @@ set as prototype on the array or object in `val`.
Throws an exception if the given prototype value is not an object.
-#### 6.55. `sleep(milliseconds)`
+#### 6.54. `sleep(milliseconds)`
Pause execution for the given amount of milliseconds. Returns `false` if
an invalid value was passed, otherwise `true`.
-#### 6.56. `assert(cond[, message])`
+#### 6.55. `assert(cond[, message])`
Raise an exception with the given `message` parameter if the value in `cond`
is not truish. When `message` is omitted, the default value is `Assertion failed`.
-#### 6.57. `render(path[, scope])`
+#### 6.56. `render(path[, scope])`
Like `include()` but capture output of included file as string and return it.
See `include()` for details on scoping.
-#### 6.58. `regexp(source[, flags])`
+#### 6.57. `regexp(source[, flags])`
Construct a regular expression instance from the given `source` pattern string
and any flags optionally specified by the `flags` argument.
@@ -1139,7 +1150,7 @@ regexp('foo.*bar', 'x'); // throws "Type error: Unrecognized flag character '
regexp('foo.*('); // throws "Syntax error: Unmatched ( or \("
```
-#### 6.59. `wildcard(subject, pattern[, nocase])`
+#### 6.58. `wildcard(subject, pattern[, nocase])`
Match the given subject against the supplied wildcard (file glob) pattern.
diff --git a/compiler.c b/compiler.c
index 889a8dd..cf9ca25 100644
--- a/compiler.c
+++ b/compiler.c
@@ -24,6 +24,7 @@
static void uc_compiler_compile_unary(uc_compiler *compiler, bool assignable);
static void uc_compiler_compile_binary(uc_compiler *compiler, bool assignable);
+static void uc_compiler_compile_delete(uc_compiler *compiler, bool assignable);
static void uc_compiler_compile_paren(uc_compiler *compiler, bool assignable);
static void uc_compiler_compile_call(uc_compiler *compiler, bool assignable);
static void uc_compiler_compile_post_inc(uc_compiler *compiler, bool assignable);
@@ -50,6 +51,7 @@ uc_compiler_parse_rules[TK_ERROR + 1] = {
[TK_ADD] = { uc_compiler_compile_unary, uc_compiler_compile_binary, P_ADD },
[TK_COMPL] = { uc_compiler_compile_unary, NULL, P_UNARY },
[TK_NOT] = { uc_compiler_compile_unary, NULL, P_UNARY },
+ [TK_DELETE] = { uc_compiler_compile_delete, NULL, P_UNARY },
[TK_INC] = { uc_compiler_compile_unary, uc_compiler_compile_post_inc, P_INC },
[TK_DEC] = { uc_compiler_compile_unary, uc_compiler_compile_post_inc, P_INC },
[TK_DIV] = { NULL, uc_compiler_compile_binary, P_MUL },
@@ -966,6 +968,57 @@ uc_compiler_compile_binary(uc_compiler *compiler, bool assignable)
}
}
+static void
+uc_compiler_compile_delete(uc_compiler *compiler, bool assignable)
+{
+ uc_chunk *chunk = uc_compiler_current_chunk(compiler);
+ enum insn_type type;
+
+ /* If the delete keyword is followed by an opening paren, it might be a
+ * legacy delete(object, propname) call */
+ if (uc_compiler_parse_match(compiler, TK_LPAREN)) {
+ uc_compiler_parse_precedence(compiler, P_ASSIGN);
+
+ if (uc_compiler_parse_match(compiler, TK_RPAREN)) {
+ type = chunk->entries[compiler->last_insn];
+
+ if (type != I_LVAL)
+ uc_compiler_syntax_error(compiler, 0,
+ "expecting a property access expression");
+
+ chunk->entries[compiler->last_insn] = I_DELETE;
+ }
+ else if (uc_compiler_parse_match(compiler, TK_COMMA)) {
+ if (uc_compiler_is_strict(compiler)) {
+ uc_compiler_syntax_error(compiler, 0,
+ "attempt to apply 'delete' operator on non-property access expression");
+ }
+ else {
+ uc_compiler_parse_precedence(compiler, P_ASSIGN);
+ uc_compiler_emit_insn(compiler, 0, I_DELETE);
+ uc_compiler_parse_consume(compiler, TK_RPAREN);
+ }
+ }
+ else {
+ uc_compiler_syntax_error(compiler, 0, "expecting ')' or ','");
+ }
+ }
+
+ /* Otherwise compile expression, ensure that it results in a property
+ * access (I_LVAL) and overwrite it with delete operation. */
+ else {
+ uc_compiler_parse_precedence(compiler, P_UNARY);
+
+ type = chunk->entries[compiler->last_insn];
+
+ if (type != I_LVAL)
+ uc_compiler_syntax_error(compiler, 0,
+ "expecting a property access expression");
+
+ chunk->entries[compiler->last_insn] = I_DELETE;
+ }
+}
+
static enum insn_type
uc_compiler_emit_variable_rw(uc_compiler *compiler, uc_value_t *varname, uc_tokentype_t type)
{
diff --git a/lexer.c b/lexer.c
index 25a5cf4..84af45d 100644
--- a/lexer.c
+++ b/lexer.c
@@ -141,6 +141,7 @@ static const struct keyword reserved_words[] = {
{ TK_ENDWHILE, "endwhile", 8, { 0 } },
{ TK_FUNC, "function", 8, { 0 } },
{ TK_DEFAULT, "default", 7, { 0 } },
+ { TK_DELETE, "delete", 6, { 0 } },
{ TK_RETURN, "return", 6, { 0 } },
{ TK_ENDFOR, "endfor", 6, { 0 } },
{ TK_SWITCH, "switch", 6, { 0 } },
diff --git a/lexer.h b/lexer.h
index 60f6ce0..e31f78a 100644
--- a/lexer.h
+++ b/lexer.h
@@ -103,6 +103,7 @@ typedef enum {
TK_REGEXP,
TK_NULL,
TK_THIS,
+ TK_DELETE,
TK_EOF,
TK_ERROR
diff --git a/lib.c b/lib.c
index e6b7135..c3a1599 100644
--- a/lib.c
+++ b/lib.c
@@ -453,35 +453,6 @@ uc_chr(uc_vm *vm, size_t nargs)
}
static uc_value_t *
-uc_delete(uc_vm *vm, size_t nargs)
-{
- uc_value_t *obj = uc_get_arg(0);
- uc_value_t *key = NULL;
- uc_value_t *rv = NULL;
- bool freeable;
- size_t i;
- char *k;
-
- if (ucv_type(obj) != UC_OBJECT)
- return NULL;
-
- for (i = 1; i < nargs; i++) {
- ucv_put(rv);
-
- key = uc_get_arg(i);
- k = uc_cast_string(vm, &key, &freeable);
- rv = ucv_get(ucv_object_get(obj, k, NULL));
-
- ucv_object_delete(obj, k);
-
- if (freeable)
- free(k);
- }
-
- return rv;
-}
-
-static uc_value_t *
uc_die(uc_vm *vm, size_t nargs)
{
uc_value_t *msg = uc_get_arg(0);
@@ -2553,7 +2524,6 @@ uc_wildcard(uc_vm *vm, size_t nargs)
static const uc_cfunction_list functions[] = {
{ "chr", uc_chr },
- { "delete", uc_delete },
{ "die", uc_die },
{ "exists", uc_exists },
{ "exit", uc_exit },
diff --git a/value.c b/value.c
index 0827038..c012b78 100644
--- a/value.c
+++ b/value.c
@@ -248,6 +248,22 @@ uc_setval(uc_vm *vm, uc_value_t *scope, uc_value_t *key, uc_value_t *val)
}
bool
+uc_delval(uc_vm *vm, uc_value_t *scope, uc_value_t *key)
+{
+ char *s;
+ bool rv;
+
+ if (!key)
+ return NULL;
+
+ s = uc_tostring(vm, key);
+ rv = ucv_object_delete(scope, s ? s : ucv_string_get(key));
+ free(s);
+
+ return rv;
+}
+
+bool
uc_cmp(int how, uc_value_t *v1, uc_value_t *v2)
{
uc_type_t t1 = ucv_type(v1);
diff --git a/value.h b/value.h
index a3abfc6..7d4e2b1 100644
--- a/value.h
+++ b/value.h
@@ -48,6 +48,7 @@ uc_type_t uc_cast_number(uc_value_t *v, int64_t *n, double *d);
uc_value_t *uc_getval(uc_vm *, uc_value_t *scope, uc_value_t *key);
uc_value_t *uc_setval(uc_vm *, uc_value_t *scope, uc_value_t *key, uc_value_t *val);
+bool uc_delval(uc_vm *, uc_value_t *scope, uc_value_t *key);
void uc_vallist_init(uc_value_list *list);
void uc_vallist_free(uc_value_list *list);
diff --git a/vm.c b/vm.c
index 5b7b5e8..8a503a0 100644
--- a/vm.c
+++ b/vm.c
@@ -1918,6 +1918,31 @@ uc_vm_insn_print(uc_vm *vm, enum insn_type insn)
ucv_put(v);
}
+static void
+uc_vm_insn_delete(uc_vm *vm, enum insn_type insn)
+{
+ uc_value_t *k = uc_vm_stack_pop(vm);
+ uc_value_t *v = uc_vm_stack_pop(vm);
+ bool rv;
+
+ switch (ucv_type(v)) {
+ case UC_OBJECT:
+ rv = uc_delval(vm, v, k);
+ uc_vm_stack_push(vm, ucv_boolean_new(rv));
+ break;
+
+ default:
+ uc_vm_raise_exception(vm, EXCEPTION_REFERENCE,
+ "left-hand side expression is %s",
+ v ? "not an object" : "null");
+
+ break;
+ }
+
+ ucv_put(k);
+ ucv_put(v);
+}
+
static uc_value_t *
uc_vm_callframe_pop(uc_vm *vm)
{
@@ -2186,6 +2211,10 @@ uc_vm_execute_chunk(uc_vm *vm)
uc_vm_insn_print(vm, insn);
break;
+ case I_DELETE:
+ uc_vm_insn_delete(vm, insn);
+ break;
+
default:
uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, "unknown opcode %d", insn);
break;
diff --git a/vm.h b/vm.h
index 30844f7..c0d81d9 100644
--- a/vm.h
+++ b/vm.h
@@ -87,7 +87,8 @@ __insn(CALL) \
__insn(MCALL) \
__insn(PRINT) \
__insn(NEXTK) \
-__insn(NEXTKV)
+__insn(NEXTKV) \
+__insn(DELETE)
#undef __insn