void psi_to_string(zval *return_value, token_t t, impl_val *ret_val, decl_var *var);
ZEND_RESULT_CODE psi_parse_args(zend_execute_data *execute_data, impl *impl);
-impl_val *psi_do_let(decl_arg *darg);
+void *psi_do_let(decl_arg *darg);
void psi_do_set(zval *return_value, set_func *func, decl_vars *vars);
void psi_do_return(impl *impl, impl_val *ret_val, zval *return_value);
void psi_do_free(free_stmt *fre);
#endif
}
-static void handler(ffi_cif *signature, void *_result, void **_args, void *_data);
+static void psi_ffi_handler(ffi_cif *signature, void *_result, void **_args, void *_data);
static inline ffi_abi psi_ffi_abi(const char *convention) {
return FFI_DEFAULT_ABI;
rc = ffi_prep_closure_loc(
data->closure,
&context->signature,
- handler,
+ psi_ffi_handler,
data,
data->code);
ZEND_ASSERT(FFI_OK == rc);
#elif PSI_HAVE_FFI_PREP_CLOSURE
- rc = ffi_prep_closure(data->code, &context->signature, handler, data);
+ rc = ffi_prep_closure(data->code, &context->signature, psi_ffi_handler, data);
ZEND_ASSERT(FFI_OK == rc);
#else
# error "Neither ffi_prep_closure() nor ffi_prep_closure_loc() available"
}
}
-static void handler(ffi_cif *_sig, void *_result, void **_args, void *_data)
+static void psi_ffi_handler(ffi_cif *_sig, void *_result, void **_args, void *_data)
{
PSI_LibffiData *data = _data;
size_t i;
- void **arg_ptr = NULL, **arg_prm = NULL;
+ void **arg_prm = NULL;
impl_val ret_val;
if (SUCCESS != psi_parse_args(*(zend_execute_data **)_args[0], data->impl)) {
}
if (data->impl->decl->args) {
- arg_ptr = malloc(data->impl->decl->args->count * sizeof(*arg_ptr));
arg_prm = malloc(data->impl->decl->args->count * sizeof(*arg_prm));
for (i = 0; i < data->impl->decl->args->count; ++i) {
decl_arg *darg = data->impl->decl->args->args[i];
- arg_ptr[i] = psi_do_let(darg);
- arg_prm[i] = (darg->let->val && darg->let->val->is_reference)
- ? &arg_ptr[i] : arg_ptr[i];
-
- darg->let->ptr = arg_ptr[i];
+ arg_prm[i] = psi_do_let(darg);
}
}
psi_do_clean(data->impl);
- if (arg_ptr) {
- free(arg_ptr);
- }
if (arg_prm) {
free(arg_prm);
}
}
-static void init(PSI_Context *C)
+static void psi_ffi_init(PSI_Context *C)
{
C->context = PSI_LibffiContextInit(NULL);
}
-static void dtor(PSI_Context *C)
+static void psi_ffi_dtor(PSI_Context *C)
{
PSI_LibffiContextFree((void *) &C->context);
}
-static zend_function_entry *compile(PSI_Context *C, PSI_Data *D)
+static zend_function_entry *psi_ffi_compile(PSI_Context *C, PSI_Data *D)
{
size_t i, j = 0;
zend_function_entry *zfe = calloc(D->impls->count + 1, sizeof(*zfe));
}
static PSI_ContextOps ops = {
- init,
- dtor,
- compile,
+ psi_ffi_init,
+ psi_ffi_dtor,
+ psi_ffi_compile,
};
PSI_ContextOps *PSI_Libffi(void)
#include <jit/jit.h>
-static void handler(jit_type_t _sig, void *result, void **_args, void *_data);
+static void psi_jit_handler(jit_type_t _sig, void *result, void **_args, void *_data);
static inline jit_abi_t psi_jit_abi(const char *convention) {
return jit_abi_cdecl;
data->params,
data->impl->decl->args->count,
1);
- data->closure = jit_closure_create(context->jit, context->signature, &handler, data);
+ data->closure = jit_closure_create(context->jit, context->signature, &psi_jit_handler, data);
context->data.list = realloc(context->data.list, ++context->data.count * sizeof(*context->data.list));
context->data.list[context->data.count-1] = data;
}
}
-static void handler(jit_type_t _sig, void *result, void **_args, void *_data)
+static void psi_jit_handler(jit_type_t _sig, void *result, void **_args, void *_data)
{
PSI_LibjitData *data = _data;
size_t i;
- void **arg_ptr = NULL, **arg_prm = NULL;
+ void **arg_prm = NULL;
impl_val ret_val;
if (SUCCESS != psi_parse_args(*(zend_execute_data **)_args[0], data->impl)) {
}
if (data->impl->decl->args->count) {
- arg_ptr = malloc(data->impl->decl->args->count * sizeof(*arg_ptr));
arg_prm = malloc(data->impl->decl->args->count * sizeof(*arg_prm));
for (i = 0; i < data->impl->decl->args->count; ++i) {
decl_arg *darg = data->impl->decl->args->args[i];
- arg_ptr[i] = psi_do_let(darg);
- arg_prm[i] = darg->let->val->is_reference ? &arg_ptr[i] : arg_ptr[i];
-
- darg->let->ptr = arg_ptr[i];
+ arg_prm[i] = psi_do_let(darg);
}
}
psi_do_clean(data->impl);
- if (arg_ptr) {
- free(arg_ptr);
- }
if (arg_prm) {
free(arg_prm);
}
}
-static void init(PSI_Context *C)
+static void psi_jit_init(PSI_Context *C)
{
C->context = PSI_LibjitContextInit(NULL);
}
-static void dtor(PSI_Context *C)
+static void psi_jit_dtor(PSI_Context *C)
{
PSI_LibjitContextFree((void *) &C->context);
}
-static zend_function_entry *compile(PSI_Context *C, PSI_Data *D)
+static zend_function_entry *psi_jit_compile(PSI_Context *C, PSI_Data *D)
{
size_t i, j = 0;
zend_function_entry *zfe = calloc(D->impls->count + 1, sizeof(*zfe));
}
static PSI_ContextOps ops = {
- init,
- dtor,
- compile,
+ psi_jit_init,
+ psi_jit_dtor,
+ psi_jit_compile,
};
PSI_ContextOps *PSI_Libjit(void)
zend_string_release(zs);
break;
}
+ /* no break */
default:
mem->zend.lval = zval_get_long(zv);
break;
size_t i, j = 0, size = decl_struct_size(s);
char *mem = ecalloc(1, size + s->args->count * sizeof(void *));
- for (i = 0; i < s->args->count; ++i) {
+ if (arr) for (i = 0; i < s->args->count; ++i) {
decl_struct_layout *layout = &s->layout[i];
decl_arg *darg = s->args->args[i];
zval *entry = zend_hash_str_find_ind(arr, darg->var->name, strlen(darg->var->name));
decl_struct_layout layout = s->layout[i];
impl_val tmp;
zval ztmp;
- char *ptr = (char *) ret_val + layout.pos;
+ char *ptr = (char *) ret_val->ptr + layout.pos;
memset(&tmp, 0, sizeof(tmp));
memcpy(&tmp, ptr, layout.len);
psi_to_string(&ztmp, real_decl_type(darg->type)->type, &tmp, darg->var);
break;
}
- /* nobreak */
+ /* no break */
case PSI_T_INT:
case PSI_T_LONG:
psi_to_int(&ztmp, real_decl_type(darg->type)->type, &tmp, darg->var);
return ecalloc(alloc->n, size);
}
-impl_val *psi_do_let(decl_arg *darg)
+void *psi_do_let(decl_arg *darg)
{
- impl_val *arg_val = &darg->let->out;
impl_arg *iarg = darg->let->arg;
+ impl_val *arg_val;
+
+ darg->let->ptr = &darg->let->out;
+ arg_val = darg->let->ptr;
if (!iarg) {
/* let foo = calloc(1, long);
} else {
memset(arg_val, 0, sizeof(*arg_val));
}
- return arg_val;
- }
-
- switch (darg->let->val->func->type) {
- case PSI_T_BOOLVAL:
- if (iarg->type->type == PSI_T_BOOL) {
- arg_val->cval = iarg->val.zend.bval;
- } else {
- arg_val->cval = zend_is_true(iarg->_zv);
- }
- break;
- case PSI_T_INTVAL:
- if (iarg->type->type == PSI_T_INT || iarg->type->type == PSI_T_LONG) {
- arg_val->lval = iarg->val.zend.lval;
- } else {
- arg_val->lval = zval_get_long(iarg->_zv);
- }
- break;
- case PSI_T_STRVAL:
- if (iarg->type->type == PSI_T_STRING) {
- arg_val->ptr = estrdup(iarg->val.zend.str->val);
- darg->let->mem = arg_val->ptr;
- zend_string_release(iarg->val.zend.str);
- } else {
- zend_string *zs = zval_get_string(iarg->_zv);
- arg_val->ptr = estrdup(zs->val);
- darg->let->mem = arg_val->ptr;
- zend_string_release(zs);
- }
- break;
- case PSI_T_STRLEN:
- if (iarg->type->type == PSI_T_STRING) {
- arg_val->lval = iarg->val.zend.str->len;
- zend_string_release(iarg->val.zend.str);
- } else {
- zend_string *zs = zval_get_string(iarg->_zv);
- arg_val->lval = zs->len;
- zend_string_release(zs);
- }
- break;
- case PSI_T_ARRVAL:
- if (iarg->type->type == PSI_T_ARRAY) {
- decl_type *type = real_decl_type(darg->type);
+ } else {
- switch (type->type) {
- case PSI_T_STRUCT:
- arg_val->ptr = psi_array_to_struct(type->strct, HASH_OF(iarg->_zv));
+ switch (darg->let->val->func->type) {
+ case PSI_T_BOOLVAL:
+ if (iarg->type->type == PSI_T_BOOL) {
+ arg_val->cval = iarg->val.zend.bval;
+ } else {
+ arg_val->cval = zend_is_true(iarg->_zv);
+ }
+ break;
+ case PSI_T_INTVAL:
+ if (iarg->type->type == PSI_T_INT || iarg->type->type == PSI_T_LONG) {
+ arg_val->lval = iarg->val.zend.lval;
+ } else {
+ arg_val->lval = zval_get_long(iarg->_zv);
+ }
+ break;
+ case PSI_T_STRVAL:
+ if (iarg->type->type == PSI_T_STRING) {
+ arg_val->ptr = estrdup(iarg->val.zend.str->val);
darg->let->mem = arg_val->ptr;
- break;
+ zend_string_release(iarg->val.zend.str);
+ } else {
+ zend_string *zs = zval_get_string(iarg->_zv);
+ arg_val->ptr = estrdup(zs->val);
+ darg->let->mem = arg_val->ptr;
+ zend_string_release(zs);
+ }
+ break;
+ case PSI_T_STRLEN:
+ if (iarg->type->type == PSI_T_STRING) {
+ arg_val->lval = iarg->val.zend.str->len;
+ zend_string_release(iarg->val.zend.str);
+ } else {
+ zend_string *zs = zval_get_string(iarg->_zv);
+ arg_val->lval = zs->len;
+ zend_string_release(zs);
+ }
+ break;
+ case PSI_T_ARRVAL:
+ if (iarg->type->type == PSI_T_ARRAY) {
+ decl_type *type = real_decl_type(darg->type);
+
+ switch (type->type) {
+ case PSI_T_STRUCT:
+ arg_val->ptr = psi_array_to_struct(type->strct, HASH_OF(iarg->_zv));
+ darg->let->mem = arg_val->ptr;
+ break;
+ }
}
+ break;
+ EMPTY_SWITCH_DEFAULT_CASE();
}
- break;
- EMPTY_SWITCH_DEFAULT_CASE();
}
- return arg_val;
+ if (darg->let->val && darg->let->val->is_reference) {
+ return &darg->let->ptr;
+ } else {
+ return darg->let->ptr;
+ }
}
void psi_do_set(zval *return_value, set_func *func, decl_vars *vars)
void PSI_ParserFree(PSI_Parser **P);
#endif
+
-#define PSI_T_COMMENT 1
-#define PSI_T_LIB 2
-#define PSI_T_QUOTED_STRING 3
-#define PSI_T_EOS 4
-#define PSI_T_STRUCT 5
-#define PSI_T_NAME 6
+#define PSI_T_NAME 1
+#define PSI_T_COMMENT 2
+#define PSI_T_LIB 3
+#define PSI_T_QUOTED_STRING 4
+#define PSI_T_EOS 5
+#define PSI_T_STRUCT 6
#define PSI_T_LBRACE 7
#define PSI_T_RBRACE 8
#define PSI_T_BOOL 9
%syntax_error {
PSI_ParserSyntaxError(P, P->fn, P->line, "Unexpected token '%s'", TOKEN->text);
}
+
+%nonassoc NAME.
+
file ::= blocks.
blocks ::= block.
type_ = init_decl_type(T->type, T->text);
free(T);
}
+decl_type(type_) ::= STRUCT(S) NAME(T). {
+ type_ = init_decl_type(S->type, T->text);
+ free(S);
+ free(T);
+}
%type impl {impl*}
%destructor impl {free_impl($$);}
s->layout = calloc(s->args->count, sizeof(*s->layout));
for (i = 0; i < s->args->count; ++i) {
decl_arg *darg = s->args->args[i];
- decl_type *type = real_decl_type(darg->type);
- token_t t = darg->var->pointer_level ? PSI_T_POINTER : type->type;
+ token_t t;
+
+ if (!validate_decl_arg(V, darg)) {
+ return 0;
+ }
+
+ t = darg->var->pointer_level
+ ? PSI_T_POINTER
+ : real_decl_type(darg->type)->type;
if (i) {
decl_struct_layout *l = &s->layout[i-1];
}
return NULL;
}
-
-static inline int validate_impl_stmts(PSI_Validator *V, impl *impl, impl_stmts *stmts) {
- /* okay,
- * - we must have exactly one ret stmt delcaring the native func to call and which type cast to apply
- * - we can have multiple let stmts; every arg of the ret stmts var (the function to call) must have one
- * - we can have any count of set stmts; processing out vars
- * - we can have any count of free stmts; freeing any out vars
- */
- size_t i, j, k;
- return_stmt *ret;
- decl *decl;
-
- if (!stmts) {
- V->error(PSI_WARNING, "Missing body for implementation %s!",
- impl->func->name);
- return 0;
- }
- if (stmts->ret.count != 1) {
- if (stmts->ret.count > 1) {
- V->error(PSI_WARNING, "Too many `ret` statements for implmentation %s;"
+static inline int validate_impl_ret_stmt(PSI_Validator *V, impl *impl) {
+ /* we must have exactly one ret stmt delcaring the native func to call */
+ /* and which type cast to apply */
+ if (impl->stmts->ret.count != 1) {
+ if (impl->stmts->ret.count > 1) {
+ V->error(PSI_WARNING, "Too many `return` statements for implmentation %s;"
" found %zu, exactly one is needed",
- impl->func->name, stmts->ret.count);
+ impl->func->name, impl->stmts->ret.count);
} else {
- V->error(PSI_WARNING, "Missing `ret` statement for implementation %s",
+ V->error(PSI_WARNING, "Missing `return` statement for implementation %s",
impl->func->name);
}
return 0;
}
-
- ret = stmts->ret.list[0];
- decl = locate_impl_decl(V->decls, ret);
- if (!decl) {
+ if (!(impl->decl = locate_impl_decl(V->decls, impl->stmts->ret.list[0]))) {
V->error(PSI_WARNING, "Missing declaration for implementation %s",
impl->func->name);
return 0;
}
+ return 1;
+}
+static inline int validate_impl_let_stmts(PSI_Validator *V, impl *impl) {
+ size_t i, j;
+ /* we can have multiple let stmts */
/* check that we have a let stmt for every decl arg */
- if (decl->args) for (i = 0; i < decl->args->count; ++i) {
- decl_arg *darg = decl->args->args[i];
+ if (impl->decl->args) for (i = 0; i < impl->decl->args->count; ++i) {
+ decl_arg *darg = impl->decl->args->args[i];
int check = 0;
- for (j = 0; j < stmts->let.count; ++j) {
- let_stmt *let = stmts->let.list[j];
+ for (j = 0; j < impl->stmts->let.count; ++j) {
+ let_stmt *let = impl->stmts->let.list[j];
if (!strcmp(let->var->name, darg->var->name)) {
darg->let = let;
V->error(PSI_WARNING, "Missing `let` statement for arg '%s %.*s%s'"
" of declaration '%s' for implementation '%s'",
darg->type->name, (int) darg->var->pointer_level, "*****",
- darg->var->name, decl->func->var->name, impl->func->name);
+ darg->var->name, impl->decl->func->var->name, impl->func->name);
return 0;
}
}
/* check that the let_value references a known variable or NULL */
- for (i = 0; i < stmts->let.count; ++i) {
- let_stmt *let = stmts->let.list[i];
+ for (i = 0; i < impl->stmts->let.count; ++i) {
+ let_stmt *let = impl->stmts->let.list[i];
int check = 0;
if (let->val && let->val->func && let->val->func->alloc) {
}
}
}
+ return 1;
+}
+static inline int validate_impl_set_stmts(PSI_Validator *V, impl *impl) {
+ size_t i, j, k;
+ /* we can have any count of set stmts; processing out vars */
/* check that set stmts reference known variables */
- for (i = 0; i < stmts->set.count; ++i) {
- set_stmt *set = stmts->set.list[i];
+ for (i = 0; i < impl->stmts->set.count; ++i) {
+ set_stmt *set = impl->stmts->set.list[i];
int check = 0;
if (impl->func->args) for (j = 0; j < impl->func->args->count; ++j) {
decl_var *set_var = set->val->vars->vars[j];
check = 0;
- if (decl->args) for (k = 0; k < decl->args->count; ++k) {
- decl_arg *set_arg = decl->args->args[k];
+ if (impl->decl->args) for (k = 0; k < impl->decl->args->count; ++k) {
+ decl_arg *set_arg = impl->decl->args->args[k];
if (!strcmp(set_var->name, set_arg->var->name)) {
check = 1;
}
}
}
- /* check free stmts */
- for (i = 0; i < stmts->fre.count; ++i) {
- free_stmt *fre = stmts->fre.list[i];
+ return 1;
+}
+static inline int validate_impl_free_stmts(PSI_Validator *V, impl *impl) {
+ size_t i, j, k;
+ /* we can have any count of free stmts; freeing any out vars */
+ for (i = 0; i < impl->stmts->fre.count; ++i) {
+ free_stmt *fre = impl->stmts->fre.list[i];
for (j = 0; j < fre->vars->count; ++j) {
decl_var *free_var = fre->vars->vars[j];
int check = 0;
- if (!strcmp(free_var->name, decl->func->var->name)) {
+ if (!strcmp(free_var->name, impl->decl->func->var->name)) {
continue;
}
- if (decl->args) for (k = 0; k < decl->args->count; ++k) {
- decl_arg *free_arg = decl->args->args[k];
+ if (impl->decl->args) for (k = 0; k < impl->decl->args->count; ++k) {
+ decl_arg *free_arg = impl->decl->args->args[k];
if (!strcmp(free_var->name, free_arg->var->name)) {
check = 1;
}
}
}
+ return 1;
+}
+static inline int validate_impl_stmts(PSI_Validator *V, impl *impl) {
+ if (!impl->stmts) {
+ V->error(PSI_WARNING, "Missing body for implementation %s!",
+ impl->func->name);
+ return 0;
+ }
- impl->decl = decl;
+ if (!validate_impl_ret_stmt(V, impl)) {
+ return 0;
+ }
+
+ if (!validate_impl_let_stmts(V, impl)) {
+ return 0;
+ }
+ if (!validate_impl_set_stmts(V, impl)) {
+ return 0;
+ }
+ if (!validate_impl_free_stmts(V, impl)) {
+ return 0;
+ }
return 1;
}
if (!validate_impl_func(V, impl, impl->func)) {
return 0;
}
- if (!validate_impl_stmts(V, impl, impl->stmts)) {
+ if (!validate_impl_stmts(V, impl)) {
return 0;
}
return 1;
--- /dev/null
+typedef long time_t;
+typedef int suseconds_t;
+
+struct timespec {
+ time_t tv_sec;
+ long tv_nsec;
+}
+
+struct timeval {
+ time_t tv_sec;
+ suseconds_t tv_usec;
+}
+
+struct timezone {
+ int tz_minuteswest;
+ int tz_dsttime;
+}
+
+struct tm {
+ int tm_sec;
+ int tm_min;
+ int tm_hour;
+ int tm_mday;
+ int tm_mon;
+ int tm_year;
+ int tm_wday;
+ int tm_yday;
+ int tm_isdst;
+ long tm_gmtoff;
+ char *tm_zone;
+}
+
+extern int gettimeofday(struct timeval *tv, struct timezone *tz);
+function psi\gettimeofday(array &$tv = NULL, array &$tz = NULL) : int {
+ let tv = calloc(1, struct timeval);
+ let tz = calloc(1, struct timezone);
+ return to_int(gettimeofday);
+ set $tv = to_array(*tv);
+ set $tz = to_array(*tz);
+}
+
+extern char *asctime(struct tm *tm);
+function psi\asctime(array $tm = NULL) : string {
+ let tm = arrval($tm);
+ return to_string(asctime);
+}
+
+
+extern struct tm *gmtime(time_t tp);
+function psi\gmtime(int $ts) : array {
+ let tp = &intval($ts);
+ return to_array(gmtime);
+}
+
+extern int nanosleep(struct timespec *rqtp, struct timespec *rmtp);
+function psi\nanosleep(array $rq = NULL, array &$rm = NULL) : int {
+ let rqtp = arrval($rq);
+ let rmtp = calloc(1, struct timespec);
+ return to_int(nanosleep);
+ set $rm = to_array(*rmtp);
+}
--- /dev/null
+--TEST--
+gettimeofday
+--INI--
+psi.directory = {PWD}
+--SKIPIF--
+<?php
+extension_loaded("psi") or die("skip - need ext/psi");
+?>
+--FILE--
+===TEST===
+<?php
+
+var_dump($ar = gettimeofday());
+
+var_dump(psi\gettimeofday());
+var_dump(psi\gettimeofday($tv), $tv);
+var_dump(psi\gettimeofday($tv, $tz), $tv, $tz);
+
+var_dump(abs($ar["sec"] - $tv["tv_sec"]) <= 1);
+?>
+===DONE===
+--EXPECTF--
+===TEST===
+array(4) {
+ ["sec"]=>
+ int(1%d)
+ ["usec"]=>
+ int(%d)
+ ["minuteswest"]=>
+ int(%d)
+ ["dsttime"]=>
+ int(%d)
+}
+int(0)
+int(0)
+array(2) {
+ ["tv_sec"]=>
+ int(1%d)
+ ["tv_usec"]=>
+ int(%d)
+}
+int(0)
+array(2) {
+ ["tv_sec"]=>
+ int(1%d)
+ ["tv_usec"]=>
+ int(%d)
+}
+array(2) {
+ ["tz_minuteswest"]=>
+ int(%d)
+ ["tz_dsttime"]=>
+ int(%d)
+}
+bool(true)
+===DONE===
+
\ No newline at end of file
--- /dev/null
+--TEST--
+asctime/gmtime
+--SKIPIF--
+<?php
+extension_loaded("psi") or die("skip - need ext/psi");
+?>
+--ENV--
+TZ=UTC
+--INI--
+psi.directory = {PWD}
+--FILE--
+===TEST===
+<?php
+var_dump(psi\asctime(NULL));
+var_dump(psi\asctime(psi\gmtime(1234567890)));
+?>
+===DONE===
+--EXPECT--
+===TEST===
+string(25) "Sun Jan 0 00:00:00 1900
+"
+string(25) "Fri Feb 13 23:31:30 2009
+"
+===DONE===
--- /dev/null
+--TEST--
+nanosleep
+--SKIPIF--
+<?php
+extension_loaded("psi") or die("skip - need ext/psi");
+?>
+--ENV--
+TZ=UTC
+--INI--
+psi.directory = {PWD}
+--FILE--
+===TEST===
+<?php
+var_dump(psi\gettimeofday($tv1), $tv1);
+var_dump(psi\nanosleep(["tv_nsec" => 10000000], $rm), $rm);
+var_dump(psi\gettimeofday($tv2), $tv2);
+var_dump($tv2["tv_usec"]-$tv1["tv_usec"]);
+?>
+===DONE===
+--EXPECTF--
+===TEST===
+int(0)
+array(2) {
+ ["tv_sec"]=>
+ int(1%d)
+ ["tv_usec"]=>
+ int(%d)
+}
+int(0)
+array(2) {
+ ["tv_sec"]=>
+ int(0)
+ ["tv_nsec"]=>
+ int(0)
+}
+int(0)
+array(2) {
+ ["tv_sec"]=>
+ int(%d)
+ ["tv_usec"]=>
+ int(%d)
+}
+int(10%r\d\d\d%r)
+===DONE===