/* * $Id$ * Copyright (c) 2014-2019 Chung, Hyung-Hwan. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WAfRRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "moo-prv.h" #include #define CLASS_BUFFER_ALIGN 64 #define LITERAL_BUFFER_ALIGN 64 #define CODE_BUFFER_ALIGN 512 #define BALIT_BUFFER_ALIGN 64 #define ARLIT_BUFFER_ALIGN 64 #define BLK_TMPRCNT_BUFFER_ALIGN 32 #define POOLDIC_OOP_BUFFER_ALIGN 32 /* initial method dictionary size */ #define INSTANCE_METHOD_DICTIONARY_SIZE 256 /* TODO: choose the right size */ #define CLASS_METHOD_DICTIONARY_SIZE 128 /* TODO: choose the right size */ #define NAMESPACE_SIZE 128 /* TODO: choose the right size - moo->option.dfl_sysdic_size may be too big for non-toplevel namespaces */ #define POOL_DICTIONARY_SIZE_ALIGN 128 #define INVALID_IP MOO_TYPE_MAX(moo_oow_t) #define PFTYPE_NONE 0 #define PFTYPE_NUMBERED 1 #define PFTYPE_NAMED 2 #define PFTYPE_EXCEPTION 3 #define PFTYPE_ENSURE 4 enum class_type_t { CLASS_TYPE_NORMAL = 0, CLASS_TYPE_EXTEND }; enum class_mod_t { CLASS_FINAL = (1 << 0), CLASS_LIMITED = (1 << 1), CLASS_INDEXED = (1 << 2), CLASS_IMMUTABLE = (1 << 3), CLASS_UNCOPYABLE = (1 << 4) }; enum var_type_t { /* == NEVER CHANGE THIS ORDER OF 3 ITEMS BELOW == * ((moo_cunit_class_t*)moo->c->cunit)->var and some iterations rely on them. */ VAR_INSTANCE = 0, VAR_CLASSINST = 1, VAR_CLASS = 2, /* == NEVER CHANGE THIS ORDER OF 3 ITEMS ABOVE == */ VAR_GLOBAL, VAR_ARGUMENT, VAR_TEMPORARY, VAR_LITERAL /* used when compiling pooldic elements only */ }; typedef enum var_type_t var_type_t; enum varacc_type_t { VARACC_GETTER = (1 << 0), VARACC_SETTER = (1 << 1) }; typedef enum varacc_type_t varacc_type_t; struct var_info_t { var_type_t type; /* not used for VAR_GLOBAL */ moo_ooi_t pos; /* useful if type is VAR_CLASS(class variable). * note it may be set to MOO_NULL to indicate the self class when * the current class being compiled has not been instantiated. */ moo_oop_class_t cls; union { moo_oop_association_t gbl; /* used for VAR_GLOBAL only */ moo_oop_t lit; /* used for VAR_LITERAL only */ } u; }; typedef struct var_info_t var_info_t; static struct voca_t { moo_oow_t len; moo_ooch_t str[11]; } vocas[] = { { 3, { 'a','n','d' } }, { 5, { 'b','r','e','a','k' } }, { 5, { '#','b','y','t','e' } }, { 10, { '#','c','h','a','r','a','c','t','e','r' } }, { 5, { 'c','l','a','s','s' } }, { 6, { '#','c','l','a','s','s' } }, { 10, { '#','c','l','a','s','s','i','n','s','t' } }, { 8, { 'c','o','n','t','i','n','u','e' } }, { 2, { 'd','o' } }, { 5, { '#','d','u','a','l' } }, { 4, { 'e','l','i','f' } }, { 7, { 'e','l','i','f','n','o','t' } }, { 4, { 'e','l','s','e' } }, { 6, { 'e','n','s','u','r','e', } }, { 9, { 'e','x','c','e','p','t','i','o','n' } }, { 6, { 'e','x','t','e','n','d' } }, { 5, { 'f','a','l','s','e' } }, { 6, { '#','f','i','n','a','l' } }, { 4, { 'f','r','o','m' } }, { 4, { '#','g','e','t' } }, { 4, { 'g','o','t','o' } }, { 9, { '#','h','a','l','f','w','o','r','d' } }, { 2, { 'i','f' } }, { 5, { 'i','f','n','o','t' } }, { 10, { '#','i','m','m','u','t','a','b','l','e' } }, { 6, { 'i','m','p','o','r','t' } }, { 8, { '#','i','n','c','l','u','d','e' } }, { 9, { 'i','n','t','e','r','f','a','c','e' } }, { 8, { '#','l','e','n','i','e','n','t' } }, { 8, { '#','l','i','b','e','r','a','l' } }, { 8, { '#','l','i','m','i','t','e','d' } }, { 7, { '#','l','i','w','o','r','d' } }, { 6, { 'm','e','t','h','o','d' } }, { 3, { 'n','i','l' } }, { 3, { 'o','f','f' } }, { 2, { 'o','n' } }, { 2, { 'o','r' } }, { 8, { '#','p','o','i','n','t','e','r' } }, { 7, { 'p','o','o','l','d','i','c' } }, { 8, { '#','p','o','o','l','d','i','c' } }, { 7, { '#','p','r','a','g','m','a' } }, { 10, { 'p','r','i','m','i','t','i','v','e',':' } }, { 10, { '#','p','r','i','m','i','t','i','v','e' } }, { 2, { 'q','c' } }, { 4, { 's','e','l','f' } }, { 6, { 's','e','l','f','n','s' } }, { 4, { '#','s','e','t' } }, { 5, { 's','u','p','e','r' } }, { 11, { 't','h','i','s','C','o','n','t','e','x','t' } }, { 11, { 't','h','i','s','P','r','o','c','e','s','s' } }, { 4, { 't','r','u','e' } }, { 11, { '#','u','n','c','o','p','y','a','b','l','e' } }, { 5, { 'u','n','t','i','l' } }, { 3, { 'v','a','r' } }, { 8, { 'v','a','r','i','a','b','l','e' } }, { 9, { '#','v','a','r','i','a','d','i','c' } }, { 5, { 'w','h','i','l','e' } }, { 5, { '#','w','o','r','d' } }, { 1, { '|' } }, { 2, { '|','+' } }, { 2, { '|','*' } }, { 1, { '>' } }, { 1, { '<' } }, { 5, { '<','E','O','F','>' } } }; enum voca_id_t { VOCA_AND, VOCA_BREAK, VOCA_BYTE_S, VOCA_CHARACTER_S, VOCA_CLASS, VOCA_CLASS_S, VOCA_CLASSINST_S, VOCA_CONTINUE, VOCA_DO, VOCA_DUAL_S, VOCA_ELIF, VOCA_ELIFNOT, VOCA_ELSE, VOCA_ENSURE, VOCA_EXCEPTION, VOCA_EXTEND, VOCA_FALSE, VOCA_FINAL_S, VOCA_FROM, VOCA_GET_S, VOCA_GOTO, VOCA_HALFWORD_S, VOCA_IF, VOCA_IFNOT, VOCA_IMMUTABLE_S, VOCA_IMPORT, VOCA_INCLUDE_S, VOCA_INTERFACE, VOCA_LENIENT_S, VOCA_LIBERAL_S, VOCA_LIMITED_S, VOCA_LIWORD_S, VOCA_METHOD, VOCA_NIL, VOCA_OFF, VOCA_ON, VOCA_OR, VOCA_POINTER_S, VOCA_POOLDIC, VOCA_POOLDIC_S, VOCA_PRAGMA_S, VOCA_PRIMITIVE_COLON, VOCA_PRIMITIVE_S, VOCA_QC, VOCA_SELF, VOCA_SELFNS, VOCA_SET_S, VOCA_SUPER, VOCA_THIS_CONTEXT, VOCA_THIS_PROCESS, VOCA_TRUE, VOCA_UNCOPYABLE_S, VOCA_UNTIL, VOCA_VAR, VOCA_VARIABLE, VOCA_VARIADIC_S, VOCA_WHILE, VOCA_WORD_S, VOCA_VBAR, VOCA_VBAR_PLUS, VOCA_VBAR_ASTER, VOCA_GT, VOCA_LT, VOCA_EOF }; typedef enum voca_id_t voca_id_t; static moo_ooch_t _nul = '\0'; static int compile_pooldic_definition (moo_t* moo); static int compile_interface_definition (moo_t* moo); static int compile_class_definition (moo_t* moo, int class_type); static int compile_block_statement (moo_t* moo); static int compile_method_statement (moo_t* moo); static int compile_method_expression (moo_t* moo, int pop); static moo_oop_t token_to_literal (moo_t* moo, int rdonly); static moo_oop_t find_element_in_compiling_pooldic (moo_t* moo, const moo_oocs_t* name); static void gc_cunit_chain (moo_t* moo); static moo_cunit_t* push_cunit (moo_t* moo, moo_cunit_type_t type); static void pop_cunit (moo_t* moo); static MOO_INLINE int is_spacechar (moo_ooci_t c) { /* TODO: handle other space unicode characters */ switch (c) { case ' ': case '\f': /* formfeed */ case '\n': /* linefeed */ case '\r': /* carriage return */ case '\t': /* horizon tab */ case '\v': /* vertical tab */ return 1; default: return 0; } } static MOO_INLINE int is_alphachar (moo_ooci_t c) { /*return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');*/ return moo_is_ooch_alpha(c); } static MOO_INLINE int is_digitchar (moo_ooci_t c) { /* TODO: support full unicode */ /*return (c >= '0' && c <= '9');*/ return moo_is_ooch_digit(c); } static MOO_INLINE int is_xdigitchar (moo_ooci_t c) { /* TODO: support full unicode */ /*return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');*/ return moo_is_ooch_xdigit(c); } static MOO_INLINE int is_alnumchar (moo_ooci_t c) { /* TODO: support full unicode */ /*return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');*/ return moo_is_ooch_alnum(c); } static MOO_INLINE int is_binselchar (moo_ooci_t c) { /* * binary-selector-character := * '&' | '*' | '+' | '-' | '/' | '%' | * '<' | '>' | '=' | '@' | '~' | '|' * * - a comma is special in moo and doesn't form a binary selector. * - an exclamation mark is excluded intentioinally because i can't tell * the method symbol #! from the comment introducer #!. * - a question mark forms a normal identifier. * - a backslash is used to form an error literal combined with a hash sign. (e.g. #\E10) */ switch (c) { case '&': case '*': case '+': case '-': case '/': case '%': case '<': case '>': case '=': case '@': case '|': case '~': return 1; default: return 0; } } static MOO_INLINE int is_leadidentchar (moo_ooci_t c) { return is_alphachar(c) || c == '_'; } static MOO_INLINE int is_identchar (moo_ooci_t c) { return is_alnumchar(c) || c == '_' || c == '?'; } #if 0 static MOO_INLINE int is_closing_char (moo_ooci_t c) { switch (c) { case '.': case '}': case ']': case ')': case ';': case '\"': case '\'': return 1; default: return 0; } }VOCA_ERROR, #endif static MOO_INLINE int is_word (const moo_oocs_t* oocs, voca_id_t id) { return oocs->len == vocas[id].len && moo_equal_oochars(oocs->ptr, vocas[id].str, vocas[id].len); } static int is_reserved_word (const moo_oocs_t* ucs, moo_iotok_type_t* token_type) { static struct { voca_id_t voca_id; moo_iotok_type_t token_type; } rw[] = { { VOCA_SELF, MOO_IOTOK_SELF }, { VOCA_SUPER, MOO_IOTOK_SUPER }, { VOCA_NIL, MOO_IOTOK_NIL }, { VOCA_TRUE, MOO_IOTOK_TRUE }, { VOCA_FALSE, MOO_IOTOK_FALSE }, { VOCA_THIS_CONTEXT, MOO_IOTOK_THIS_CONTEXT }, { VOCA_THIS_PROCESS, MOO_IOTOK_THIS_PROCESS }, { VOCA_SELFNS, MOO_IOTOK_SELFNS }, { VOCA_IF, MOO_IOTOK_IF }, { VOCA_IFNOT, MOO_IOTOK_IFNOT }, { VOCA_ELIF, MOO_IOTOK_ELIF }, { VOCA_ELIFNOT, MOO_IOTOK_ELIFNOT }, { VOCA_ELSE, MOO_IOTOK_ELSE }, { VOCA_WHILE, MOO_IOTOK_WHILE }, { VOCA_UNTIL, MOO_IOTOK_UNTIL }, { VOCA_DO, MOO_IOTOK_DO }, { VOCA_BREAK, MOO_IOTOK_BREAK }, { VOCA_CONTINUE, MOO_IOTOK_CONTINUE }, { VOCA_GOTO, MOO_IOTOK_GOTO }, { VOCA_AND, MOO_IOTOK_AND }, { VOCA_OR, MOO_IOTOK_OR } }; int i; for (i = 0; i < MOO_COUNTOF(rw); i++) { if (is_word(ucs, rw[i].voca_id)) { if (token_type) *token_type = rw[i].token_type; return 1; } } return 0; } #if 0 static int is_restricted_word (const moo_oocs_t* ucs) { /* not fully reserved. but restricted in a certain context */ static int rw[] = { VOCA_CLASS, VOCA_EXTEND, VOCA_FROM, VOCA_IMPORT, VOCA_METHOD, VOCA_POOLDIC, VOCA_VAR, VOCA_VARIABLE }; int i; for (i = 0; i < MOO_COUNTOF(rw); i++) { if (is_word(ucs, rw[i])) return 1; } return 0; } #endif static int begin_include (moo_t* moo); static int end_include (moo_t* moo); static int copy_string_to (moo_t* moo, const moo_oocs_t* src, moo_oocs_t* dst, moo_oow_t* dst_capa, int append, moo_ooch_t delim_char) { moo_oow_t len, pos; if (append) { pos = dst->len; len = dst->len + src->len; if (delim_char != '\0') len++; } else { pos = 0; len = src->len; } if (len >= *dst_capa) { moo_ooch_t* tmp; moo_oow_t capa; capa = MOO_ALIGN(len + 1, CLASS_BUFFER_ALIGN); tmp = (moo_ooch_t*)moo_reallocmem(moo, dst->ptr, MOO_SIZEOF(*tmp) * capa); if (!tmp) return -1; dst->ptr = tmp; *dst_capa = capa - 1; } if (append && delim_char != '\0') dst->ptr[pos++] = delim_char; moo_copy_oochars (&dst->ptr[pos], src->ptr, src->len); dst->ptr[len] = '\0'; dst->len = len; return 0; } static int find_word_in_string (const moo_oocs_t* haystack, const moo_oocs_t* name, moo_oow_t* xindex) { /* this function is inefficient. but considering the typical number * of arguments and temporary variables, the inefficiency can be * ignored in my opinion. the overhead to maintain the reverse lookup * table from a name to an index should be greater than this simple * inefficient lookup */ moo_ooch_t* t, * e; moo_oow_t index, i; t = haystack->ptr; e = t + haystack->len; index = 0; while (t < e) { while (t < e && is_spacechar(*t)) t++; for (i = 0; i < name->len; i++) { if (t >= e || name->ptr[i] != *t) goto unmatched; t++; } if (t >= e || is_spacechar(*t)) { if (xindex) *xindex = index; return 0; } unmatched: while (t < e) { if (is_spacechar(*t)) { t++; break; } t++; } index++; } return -1; } static int fetch_word_from_string (const moo_oocs_t* haystack, moo_oow_t xindex, moo_oocs_t* str) { moo_ooch_t* t, * e, * ss; moo_oow_t index; t = haystack->ptr; e = t + haystack->len; index = 0; while (t < e) { while (t < e && is_spacechar(*t)) t++; ss = t; while (t < e && !is_spacechar(*t)) t++; if (xindex == index) { str->ptr = ss; str->len = t - ss; return 0; } index++; } return -1; } static int find_oop_in_oopbuf (moo_t* moo, moo_oopbuf_t* oopbuf, moo_oow_t start, moo_oow_t step, moo_oop_t item, moo_oow_t* index) { moo_oow_t i; for (i = start; i < oopbuf->count; i += step) { if (oopbuf->ptr[i] == item) { if (index) *index = i; return 0; } } moo_seterrnum (moo, MOO_ENOENT); return -1; } static int add_oop_to_oopbuf (moo_t* moo, moo_oopbuf_t* oopbuf, moo_oop_t item) { if (oopbuf->count >= oopbuf->capa) { moo_oop_t* tmp; moo_oow_t new_capa; new_capa = MOO_ALIGN(oopbuf->count + 1, ARLIT_BUFFER_ALIGN); tmp = (moo_oop_t*)moo_reallocmem(moo, oopbuf->ptr, new_capa * MOO_SIZEOF(*tmp)); if (!tmp) return -1; oopbuf->capa = new_capa; oopbuf->ptr = tmp; } /* TODO: overflow check of oopbuf->count itself */ oopbuf->ptr[oopbuf->count++] = item; return 0; } static int add_oop_to_oopbuf_nodup (moo_t* moo, moo_oopbuf_t* oopbuf, moo_oop_t item, moo_oow_t* index) { moo_oow_t i; for (i = 0; i < oopbuf->count; i++) { if (oopbuf->ptr[i] == item) { *index = i; return 0; /* the same item exists */ } } if (add_oop_to_oopbuf(moo, oopbuf, item) <= -1) return -1; *index = oopbuf->count - 1; return 1; /* added a new item */ } #define ZDIGIT_TO_NUM(c,base) \ ((c >= '0' && c <= '9')? (c - '0'): \ (c >= 'A' && c <= 'Z')? (c - 'A' + 10): \ (c >= 'a' && c <= 'z')? (c - 'a' + 10): base) static int string_to_smooi (moo_t* moo, moo_oocs_t* str, int radixed, moo_ooi_t* num) { /* it is not a generic conversion function. * it assumes a certain pre-sanity check on the string * done by the lexical analyzer */ int v, negsign, base; const moo_ooch_t* ptr, * end; moo_oow_t value, old_value; negsign = 0; ptr = str->ptr, end = str->ptr + str->len; MOO_ASSERT (moo, ptr < end); if (*ptr == '+' || *ptr == '-') { negsign = *ptr - '+'; ptr++; } if (radixed) { MOO_ASSERT (moo, ptr < end); /* the caller must ensure that the radix part is composed of decimal digits only */ base = 0; do { base = base * 10 + ZDIGIT_TO_NUM(*ptr, 10); ptr++; } while (*ptr != 'r'); ptr++; } else base = 10; MOO_ASSERT (moo, ptr < end); value = old_value = 0; while (ptr < end && (v = ZDIGIT_TO_NUM(*ptr, base)) < base) { value = value * base + v; if (value < old_value) { /* overflow must have occurred */ moo_seterrnum (moo, MOO_ERANGE); return -1; } old_value = value; ptr++; } if (ptr < end) { /* trailing garbage? */ moo_seterrnum (moo, MOO_EINVAL); return -1; } MOO_ASSERT (moo, -MOO_SMOOI_MAX == MOO_SMOOI_MIN); if (value > MOO_SMOOI_MAX) { moo_seterrnum (moo, MOO_ERANGE); return -1; } *num = value; if (negsign) *num *= -1; return 0; } static moo_oop_t string_to_int (moo_t* moo, moo_oocs_t* str, int radixed) { int negsign, base; const moo_ooch_t* ptr, * end; negsign = 0; ptr = str->ptr, end = str->ptr + str->len; MOO_ASSERT (moo, ptr < end); if (*ptr == '+' || *ptr == '-') { negsign = *ptr - '+'; ptr++; } if (radixed) { MOO_ASSERT (moo, ptr < end); /* the caller must ensure that the radix part is composed of decimal digits only */ base = 0; do { base = base * 10 + ZDIGIT_TO_NUM(*ptr, 10); ptr++; } while (*ptr != 'r'); ptr++; } else base = 10; /* TODO: handle floating point numbers ... etc */ if (negsign) base = -base; return moo_strtoint(moo, ptr, end - ptr, base); } static moo_oop_t string_to_fpdec (moo_t* moo, moo_oocs_t* str, int prescaled) { moo_oow_t pos, len; moo_oow_t scale = 0, xscale = 0; moo_oop_t v; int base = 10, dotted = 0; pos = str->len; while (pos > 0) { if (str->ptr[--pos] == '.') { dotted = 1; scale = str->len - pos - 1; MOO_ASSERT (moo, scale > 0); MOO_ASSERT (moo, scale <= MOO_SMOOI_MAX); MOO_MEMMOVE (&str->ptr[pos], &str->ptr[pos + 1], scale * MOO_SIZEOF(str->ptr[0])); /* remove the decimal point from the string */ break; } } pos = 0; len = str->len - dotted; if (str->ptr[pos] == '+' || str->ptr[pos] == '-') { if (str->ptr[pos] - '+') base = -10; pos++; len--; } if (prescaled) { do { xscale = xscale * 10 + ZDIGIT_TO_NUM(str->ptr[pos], 10); pos++; len--; } while (str->ptr[pos] != 'p'); pos++; len--; } else xscale = scale; /* the caller guarantees that the scale is greater than 0 if not pre-scaled. * it might be zero if pre-scaled. but it guarantees that the prescale is * greater than 0 */ if (scale < xscale) { /* need to add more zeros */ moo_oow_t explen; explen = len + xscale - scale; if (moo_copyoocharstosbuf(moo, &str->ptr[pos], len, MOO_SBUF_ID_FPDEC) <= -1 || moo_concatoochartosbuf(moo, '0', explen - len, MOO_SBUF_ID_FPDEC) <= -1) { const moo_ooch_t* oldmsg = moo_backuperrmsg(moo); moo_seterrbfmt (moo, moo_geterrnum(moo), "unable to convert to fpdec %.*js - %js", str->len, str->ptr, oldmsg); return MOO_NULL; } v = moo_strtoint(moo, moo->sbuf[MOO_SBUF_ID_FPDEC].ptr, moo->sbuf[MOO_SBUF_ID_FPDEC].len, base); scale = xscale; } else if (scale > xscale) { v = moo_strtoint(moo, &str->ptr[pos], len - (scale - xscale), base); scale = xscale; } else { v = moo_strtoint(moo, &str->ptr[pos], len, base); } if (!v) { const moo_ooch_t* oldmsg = moo_backuperrmsg(moo); moo_seterrbfmt (moo, moo_geterrnum(moo), "unable to convert to fpdec %.*js - %js", str->len, str->ptr, oldmsg); return MOO_NULL; } return moo_makefpdec(moo, v, scale); } static moo_oop_t string_to_error (moo_t* moo, moo_oocs_t* str, moo_ioloc_t* loc) { moo_ooi_t num = 0; const moo_ooch_t* ptr, * end; ptr = str->ptr, end = str->ptr + str->len; /* i assume that the input is in the form of \#ENNN * all other letters are non-digits except the NNN part. * i just skip all non-digit letters for simplicity sake. */ while (ptr < end) { if (is_digitchar(*ptr)) { moo_oow_t xnum; xnum = num * 10 + (*ptr - '0'); if (xnum < num || xnum > MOO_ERROR_MAX) { /* overflowed */ moo_setsynerr (moo, MOO_SYNERR_ERRLITINVAL, loc, str); return MOO_NULL; } num = xnum; } ptr++; } return MOO_ERROR_TO_OOP(num); } static moo_oop_t string_to_ptr (moo_t* moo, moo_oocs_t* str, moo_ioloc_t* loc) { moo_oow_t num = 0; const moo_ooch_t* ptr, * end; moo_oop_t ret; ptr = str->ptr, end = str->ptr + str->len; /* i assume that the input is in the form of \#PNNN * all other letters are non-xdigits except the NNN part. * i just skip all non-digit letters for simplicity sake. */ while (ptr < end) { if (is_xdigitchar(*ptr)) { moo_oow_t xnum; xnum = num * 16; if (*ptr >= 'a' && *ptr <= 'f') xnum += (*ptr - 'a' + 10); else if (*ptr >= 'A' && *ptr <= 'F') xnum += (*ptr - 'A' + 10); else xnum += (*ptr - '0'); if (xnum < num) { /* overflowed */ moo_setsynerr (moo, MOO_SYNERR_SMPTRLITINVAL, loc, str); return MOO_NULL; } num = xnum; } ptr++; } return moo_oowtoptr(moo, num); } /* --------------------------------------------------------------------- * SOME PRIVIATE UTILITILES * --------------------------------------------------------------------- */ static void init_oow_pool (moo_t* moo, moo_oow_pool_t* pool) { pool->count = 0; pool->static_chunk.next = MOO_NULL; pool->head = &pool->static_chunk; pool->tail = &pool->static_chunk; } static void fini_oow_pool (moo_t* moo, moo_oow_pool_t* pool) { moo_oow_pool_chunk_t* chunk, * next; /* dispose all chunks except the first static one */ chunk = pool->head->next; while (chunk) { next = chunk->next; moo_freemem (moo, chunk); chunk = next; } /* this doesn't reinitialize the pool. call init_oow_pool() * to reuse it */ } static int add_to_oow_pool (moo_t* moo, moo_oow_pool_t* pool, moo_oow_t v, const moo_ioloc_t* loc) { moo_oow_t idx; idx = pool->count % MOO_COUNTOF(pool->static_chunk.buf); if (pool->count > 0 && idx == 0) { moo_oow_pool_chunk_t* chunk; chunk = (moo_oow_pool_chunk_t*)moo_allocmem(moo, MOO_SIZEOF(pool->static_chunk)); if (!chunk) return -1; chunk->next = MOO_NULL; pool->tail->next = chunk; pool->tail = chunk; } pool->tail->buf[idx].v = v; pool->tail->buf[idx].loc = *loc; pool->count++; return 0; } static MOO_INLINE moo_method_data_t* get_cunit_method_data (moo_t* moo) { static moo_oow_t offset[] = /* [NOTE] this is dependent on the order of moo_cunit_type_t enumerators */ { 0, /* blank */ 0, /* pooldic */ MOO_OFFSETOF(moo_cunit_class_t, mth), MOO_OFFSETOF(moo_cunit_interface_t, mth) }; MOO_ASSERT (moo, moo->c->cunit && (moo->c->cunit->cunit_type == MOO_CUNIT_CLASS || moo->c->cunit->cunit_type == MOO_CUNIT_INTERFACE)); return (moo_method_data_t*)((moo_uint8_t*)moo->c->cunit + offset[moo->c->cunit->cunit_type]); } static MOO_INLINE moo_oop_t get_cunit_self_oop (moo_t* moo) { static moo_oow_t offset[] = /* [NOTE] this is dependent on the order of moo_cunit_type_t enumerators */ { 0, /* blank */ 0, /* pooldic */ MOO_OFFSETOF(moo_cunit_class_t, self_oop), MOO_OFFSETOF(moo_cunit_interface_t, self_oop) }; MOO_ASSERT (moo, moo->c->cunit && (moo->c->cunit->cunit_type == MOO_CUNIT_CLASS || moo->c->cunit->cunit_type == MOO_CUNIT_INTERFACE)); return *(moo_oop_t*)((moo_uint8_t*)moo->c->cunit + offset[moo->c->cunit->cunit_type]); } #if 0 static MOO_INLINE moo_oow_t get_cunit_dbgi_offset (moo_t* moo) { static moo_oow_t offset[] = /* [NOTE] this is dependent on the order of moo_cunit_type_t enumerators */ { 0, /* blank */ 0, /* pooldic */ MOO_OFFSETOF(moo_cunit_class_t, dbgi_class_offset), MOO_OFFSETOF(moo_cunit_interface_t, dbgi_interface_offset) }; MOO_ASSERT (moo, moo->c->cunit && (moo->c->cunit->cunit_type == MOO_CUNIT_CLASS || moo->c->cunit->cunit_type == MOO_CUNIT_INTERFACE)); return *(moo_oow_t*)((moo_uint8_t*)moo->c->cunit + offset[moo->c->cunit->cunit_type]); } #endif static MOO_INLINE moo_oocs_t* get_cunit_fqn (moo_t* moo) { static moo_oow_t offset[] = /* [NOTE] this is dependent on the order of moo_cunit_type_t enumerators */ { 0, /* blank */ 0, /* pooldic */ MOO_OFFSETOF(moo_cunit_class_t, fqn), MOO_OFFSETOF(moo_cunit_interface_t, fqn) }; MOO_ASSERT (moo, moo->c->cunit && (moo->c->cunit->cunit_type == MOO_CUNIT_CLASS || moo->c->cunit->cunit_type == MOO_CUNIT_INTERFACE)); return (moo_oocs_t*)((moo_uint8_t*)moo->c->cunit + offset[moo->c->cunit->cunit_type]); } /* --------------------------------------------------------------------- * Tokenizer * --------------------------------------------------------------------- */ #define GET_CHAR(moo) \ do { if (get_char(moo) <= -1) return -1; } while (0) #define GET_CHAR_TO(moo,c) \ do { \ if (get_char(moo) <= -1) return -1; \ c = (moo)->c->lxc.c; \ } while(0) #define GET_TOKEN(moo) \ do { if (get_token(moo) <= -1) return -1; } while (0) #define GET_TOKEN_RETURN(moo,fail_ret) \ do { if (get_token(moo) <= -1) return fail_ret; } while (0) #define GET_TOKEN_GOTO(moo,fail_label) \ do { if (get_token(moo) <= -1) goto fail_label; } while (0) #define ADD_TOKEN_STR(moo,s,l) \ do { if (add_token_str(moo, s, l) <= -1) return -1; } while (0) #define ADD_TOKEN_CHAR(moo,c) \ do { if (add_token_char(moo, (moo_ooch_t)c) <= -1) return -1; } while (0) #define CLEAR_TOKEN_NAME(moo) ((moo)->c->tok.name.len = 0) #define SET_TOKEN_TYPE(moo,tv) ((moo)->c->tok.type = (tv)) #define TOKEN_TYPE(moo) ((moo)->c->tok.type) #define TOKEN_NAME(moo) (&(moo)->c->tok.name) #define TOKEN_NAME_CAPA(moo) ((moo)->c->tok.name_capa) #define TOKEN_NAME_PTR(moo) ((moo)->c->tok.name.ptr) #define TOKEN_NAME_LEN(moo) ((moo)->c->tok.name.len) #define TOKEN_LOC(moo) (&(moo)->c->tok.loc) #define LEXER_LOC(moo) (&(moo)->c->lxc.l) static MOO_INLINE int does_token_name_match (moo_t* moo, voca_id_t id) { return TOKEN_NAME_LEN(moo) == vocas[id].len && moo_equal_oochars(TOKEN_NAME_PTR(moo), vocas[id].str, vocas[id].len); } static MOO_INLINE int is_token_symbol (moo_t* moo, voca_id_t id) { return TOKEN_TYPE(moo) == MOO_IOTOK_SYMLIT && does_token_name_match(moo, id); } static MOO_INLINE int is_token_word (moo_t* moo, voca_id_t id) { return TOKEN_TYPE(moo) == MOO_IOTOK_IDENT && does_token_name_match(moo, id); } static MOO_INLINE int is_token_binary_selector (moo_t* moo, voca_id_t id) { return TOKEN_TYPE(moo) == MOO_IOTOK_BINSEL && does_token_name_match(moo, id); } static MOO_INLINE int is_token_keyword (moo_t* moo, voca_id_t id) { return TOKEN_TYPE(moo) == MOO_IOTOK_KEYWORD && does_token_name_match(moo, id); } static MOO_INLINE int add_token_str (moo_t* moo, const moo_ooch_t* ptr, moo_oow_t len) { moo_oocs_t tmp; tmp.ptr = (moo_ooch_t*)ptr; tmp.len = len; return copy_string_to (moo, &tmp, TOKEN_NAME(moo), &TOKEN_NAME_CAPA(moo), 1, '\0'); } static MOO_INLINE int add_token_char (moo_t* moo, moo_ooch_t c) { moo_oocs_t tmp; tmp.ptr = &c; tmp.len = 1; return copy_string_to (moo, &tmp, TOKEN_NAME(moo), &TOKEN_NAME_CAPA(moo), 1, '\0'); } static MOO_INLINE void unget_char (moo_t* moo, const moo_iolxc_t* c) { /* Make sure that the unget buffer is large enough */ MOO_ASSERT (moo, moo->c->nungots < MOO_COUNTOF(moo->c->ungot)); moo->c->ungot[moo->c->nungots++] = *c; } static int get_char (moo_t* moo) { moo_ooi_t n; moo_ooci_t lc; if (moo->c->nungots > 0) { /* something in the unget buffer */ moo->c->lxc = moo->c->ungot[--moo->c->nungots]; return 0; } if (moo->c->curinp->b.state == -1) { moo->c->curinp->b.state = 0; return -1; } else if (moo->c->curinp->b.state == 1) { moo->c->curinp->b.state = 0; goto return_eof; } if (moo->c->curinp->b.pos >= moo->c->curinp->b.len) { n = moo->c->impl(moo, MOO_IO_READ, moo->c->curinp); if (n <= -1) return -1; if (n == 0) { return_eof: moo->c->curinp->lxc.c = MOO_OOCI_EOF; moo->c->curinp->lxc.l.line = moo->c->curinp->line; moo->c->curinp->lxc.l.colm = moo->c->curinp->colm; moo->c->curinp->lxc.l.file = moo->c->curinp->name; moo->c->lxc = moo->c->curinp->lxc; /* indicate that EOF has been read. lxc.c is also set to EOF. */ return 0; } moo->c->curinp->b.pos = 0; moo->c->curinp->b.len = n; } if (moo->c->curinp->lxc.c == '\n' || moo->c->curinp->lxc.c == '\r') { /* moo->c->curinp->lxc.c is a previous character. the new character * to be read is still in the buffer (moo->c->curinp->buf). * moo->cu->curinp->colm has been incremented when the previous * character has been read. */ if (moo->c->curinp->line > 1 && moo->c->curinp->colm == 2 && moo->c->curinp->nl != moo->c->curinp->lxc.c) { /* most likely, it's the second character in '\r\n' or '\n\r' * sequence. let's not update the line and column number. */ /*moo->c->curinp->colm = 1;*/ } else { /* if the previous charater was a newline, * increment the line counter and reset column to 1. * incrementing the line number here instead of * updating inp->lxc causes the line number for * TOK_EOF to be the same line as the lxc newline. */ moo->c->curinp->line++; moo->c->curinp->colm = 1; moo->c->curinp->nl = moo->c->curinp->lxc.c; } } lc = moo->c->curinp->buf[moo->c->curinp->b.pos++]; moo->c->curinp->lxc.c = lc; moo->c->curinp->lxc.l.line = moo->c->curinp->line; moo->c->curinp->lxc.l.colm = moo->c->curinp->colm++; moo->c->curinp->lxc.l.file = moo->c->curinp->name; moo->c->lxc = moo->c->curinp->lxc; return 1; /* indicate that a normal character has been read */ } static MOO_INLINE int skip_spaces (moo_t* moo) { while (is_spacechar(moo->c->lxc.c)) GET_CHAR (moo); return 0; } static int skip_comment (moo_t* moo) { moo_ooci_t c = moo->c->lxc.c; moo_iolxc_t lc; if ((moo->c->pragma_flags & MOO_PRAGMA_QC) && c == '"') { /* skip up to the closing " */ do { GET_CHAR_TO (moo, c); if (c == MOO_OOCI_EOF) goto unterminated; } while (c != '"'); if (c == '"') GET_CHAR (moo); /* keep the next character in lxc */ return 1; /* double-quoted comment */ } else if (c == '/') { /* handle block comment encoded in /x x/ where x is * */ lc = moo->c->lxc; GET_CHAR_TO (moo, c); if (c == '/') { do { GET_CHAR_TO (moo, c); if (c == MOO_OOCI_EOF) { /* EOF on the comment line is ok for a single-line comment */ break; } else if (c == '\r' || c == '\n') { GET_CHAR (moo); /* keep the first meaningful character in lxc */ break; } } while (1); return 1; /* single line comment led by // */ } else if (c == '*') { do { GET_CHAR_TO (moo, c); if (c == MOO_OOCI_EOF) goto unterminated; if (c == '*') { check_rparen: GET_CHAR_TO (moo, c); if (c == MOO_OOCI_EOF) goto unterminated; if (c == '*') goto check_rparen; /* got another * after * */ if (c == '/') { GET_CHAR (moo); /* keep the first meaningful character in lxc */ break; } } } while (1); return 1; /* multi-line comment enclosed in /x and x/ where x is * */ } goto not_comment; } else if (c == '#') { /* handle #! */ /* save the last character */ lc = moo->c->lxc; /* read a new character */ GET_CHAR_TO (moo, c); if (c != '!') goto not_comment; do { GET_CHAR_TO (moo, c); if (c == MOO_OOCI_EOF) { /* EOF on the comment line is ok for a single-line comment */ break; } else if (c == '\r' || c == '\n') { GET_CHAR (moo); /* keep the first meaningful character in lxc */ break; } } while (1); return 1; /* single line comment led by #! */ } else { /* not comment. but no next character has been consumed. * no need to unget a character */ return 0; } not_comment: /* unget the leading '#' */ unget_char (moo, &moo->c->lxc); /* restore the previous state */ moo->c->lxc = lc; return 0; unterminated: moo_setsynerr (moo, MOO_SYNERR_CMTNC, LEXER_LOC(moo), MOO_NULL); return -1; } static int get_ident (moo_t* moo, moo_ooci_t char_read_ahead) { /* * identifier := alpha-char (alpha-char | digit-char)* * keyword := identifier ":" */ moo_ooci_t c; c = moo->c->lxc.c; SET_TOKEN_TYPE (moo, MOO_IOTOK_IDENT); if (char_read_ahead != MOO_OOCI_EOF) { ADD_TOKEN_CHAR (moo, char_read_ahead); } /* while() instead of do..while() because when char_read_ahead is not EOF * c may not be a identifier character */ while (is_identchar(c)) { ADD_TOKEN_CHAR (moo, c); GET_CHAR_TO (moo, c); } if (c == ':') { #if 0 read_more_kwsym: ADD_TOKEN_CHAR (moo, c); SET_TOKEN_TYPE (moo, MOO_IOTOK_KEYWORD); GET_CHAR_TO (moo, c); if (moo->c->in_array && is_leadidentchar(c)) { /* when reading an array literal, read as many characters as * would compose a normal keyword symbol literal. * for example, in #(a #b:c: x:y:) x:y: is not preceded * by #. in an array literal, it should still be treated as * a symbol. */ do { ADD_TOKEN_CHAR (moo, c); GET_CHAR_TO (moo, c); } while (is_identchar(c)); if (c == ':') goto read_more_kwsym; else { /* the last character is not a colon */ moo_setsynerr (moo, MOO_SYNERR_COLON, LEXER_LOC(moo), MOO_NULL); return -1; } } else { unget_char (moo, &moo->c->lxc); } #else moo_iolxc_t lc = moo->c->lxc; GET_CHAR_TO (moo, c); if (c == '=' || c == '{') { /* := or :{ appeared after an identifier */ unget_char (moo, &moo->c->lxc); unget_char (moo, &lc); } else { ADD_TOKEN_CHAR (moo, lc.c); SET_TOKEN_TYPE (moo, MOO_IOTOK_KEYWORD); unget_char (moo, &moo->c->lxc); } #endif } else { moo_iotok_type_t token_type; if (c == '.') { moo_iolxc_t period; period = moo->c->lxc; read_more_seg: GET_CHAR_TO (moo, c); if (is_leadidentchar(c)) { SET_TOKEN_TYPE (moo, MOO_IOTOK_IDENT_DOTTED); ADD_TOKEN_CHAR (moo, '.'); do { ADD_TOKEN_CHAR (moo, c); GET_CHAR_TO (moo, c); } while (is_identchar(c)); if (c == '.') goto read_more_seg; unget_char (moo, &moo->c->lxc); } else { unget_char (moo, &moo->c->lxc); /* unget the period itself */ unget_char (moo, &period); } } else { unget_char (moo, &moo->c->lxc); } if (is_reserved_word(TOKEN_NAME(moo), &token_type)) { /* handle reserved words */ SET_TOKEN_TYPE (moo, token_type); } } return 0; } static int get_numlit (moo_t* moo, int negated) { /* * number-literal := number | ("-" number) * number := integer | float | scaledDecimal * integer := decimal-integer | radix-integer * decimal-integer := digit-char+ * radix-integer := radix-specifier "r" radix-digit+ * radix-specifier := digit-char+ * radix-digit := digit-char | upper-alpha-char * * float := mantissa [exponentLetter exponent] * mantissa := digit-char+ "." digit-char+ * exponent := ['-'] decimal-integer * exponentLetter := 'e' | 'd' | 'q' * scaledDecimal := scaledMantissa 's' [fractionalDigits] * scaledMantissa := decimal-integer | mantissa * fractionalDigits := decimal-integer */ moo_ooci_t c; moo_oow_t radix = 0, xscale = 0; int radix_overflowed = 0; c = moo->c->lxc.c; SET_TOKEN_TYPE (moo, MOO_IOTOK_INTLIT); /*TODO: support a complex numeric literal */ do { /* collect the potential radix specifier */ if (!radix_overflowed) { int r; moo_oow_t rv; r = ZDIGIT_TO_NUM(c, 10); MOO_ASSERT (moo, r < 10); rv = radix * 10 + r; if (rv < radix) radix_overflowed = 1; radix = rv; } ADD_TOKEN_CHAR (moo, c); GET_CHAR_TO (moo, c); if (c == '_') { /* i allow digit separation with _ as in 123_456. */ moo_iolxc_t underscore; underscore = moo->c->lxc; GET_CHAR_TO(moo, c); if (!is_digitchar(c)) { unget_char (moo, &moo->c->lxc); unget_char (moo, &underscore); break; } else continue; } } while (is_digitchar(c)); if (c == 'p') { /* fixed-point decimal with the scale specified. * 5p99 -> 99.0000, 5p99.12 -> 99.12000 * treat the radix value as the scale */ if (radix_overflowed || radix < 1 || radix > MOO_SMOOI_MAX) { moo_setsynerr (moo, MOO_SYNERR_FPDECSCALEINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } xscale = radix; radix = 10; goto radixed; /* not really radixed. but use the same code. will eventually jump to fixed-point */ } else if (c == 'r') { /* radix specifier */ if (radix_overflowed || radix < 2 || radix > 36) { /* radix too big */ moo_setsynerr (moo, MOO_SYNERR_RADIXINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } radixed: ADD_TOKEN_CHAR (moo, c); GET_CHAR_TO (moo, c); if (ZDIGIT_TO_NUM(c, radix) >= radix) { /* no digit after the radix specifier */ moo_setsynerr (moo, (xscale > 0? MOO_SYNERR_FPDECLITINVAL: MOO_SYNERR_RADINTLITINVAL), TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } do { ADD_TOKEN_CHAR (moo, c); GET_CHAR_TO (moo, c); if (xscale > 0 && c == '.') goto fixed_point; if (c == '_') { moo_iolxc_t underscore; underscore = moo->c->lxc; GET_CHAR_TO(moo, c); if (ZDIGIT_TO_NUM(c, radix) >= radix) { unget_char (moo, &moo->c->lxc); unget_char (moo, &underscore); break; } else continue; } } while (ZDIGIT_TO_NUM(c, radix) < radix); SET_TOKEN_TYPE (moo, (xscale > 0? MOO_IOTOK_SCALEDFPDECLIT: MOO_IOTOK_RADINTLIT)); unget_char (moo, &moo->c->lxc); } else if (c == '.') { moo_iolxc_t period; moo_oow_t scale; fixed_point: scale = 0; /* once a jump is made to here. it's known to be a fixed point number. * xscale is the scale prefix specified before 'p' as in 10p99.02. * scale is the number of digits after the decimal point. */ SET_TOKEN_TYPE (moo, (xscale > 0? MOO_IOTOK_SCALEDFPDECLIT: MOO_IOTOK_FPDECLIT)); period = moo->c->lxc; GET_CHAR_TO (moo, c); if (!is_digitchar(c)) { unget_char (moo, &moo->c->lxc); unget_char (moo, &period); if (xscale <= 0) { /* * restore the token type. it's not prefixed with 'p' like 20p. * the number is followed by a terminating period rather than a decimal point. */ SET_TOKEN_TYPE(moo, MOO_IOTOK_INTLIT); } } else { ADD_TOKEN_CHAR (moo, '.'); do { if (scale > MOO_SMOOI_MAX) { moo_setsynerrbfmt (moo, MOO_SYNERR_FPDECLITINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo), "invalid fixed-point decimal - too many digits(%zu) after point", scale); return -1; } scale++; ADD_TOKEN_CHAR (moo, c); GET_CHAR_TO (moo, c); if (c == '_') { moo_iolxc_t underscore; underscore = moo->c->lxc; GET_CHAR_TO(moo, c); if (!is_digitchar(c)) { unget_char (moo, &moo->c->lxc); unget_char (moo, &underscore); break; } else continue; } } while (is_digitchar(c)); MOO_ASSERT (moo, scale > 0 && scale <= MOO_SMOOI_MAX); unget_char (moo, &moo->c->lxc); } } else { unget_char (moo, &moo->c->lxc); } /* * TODO: handle floating point number */ return 0; } static int get_charlit (moo_t* moo) { /* * character-literal := "$" character * character := normal-character | "'" */ moo_ooci_t c = moo->c->lxc.c; /* even a new-line or white space would be taken */ if (c == MOO_OOCI_EOF) { moo_setsynerr (moo, MOO_SYNERR_CLTNT, LEXER_LOC(moo), MOO_NULL); return -1; } SET_TOKEN_TYPE (moo, MOO_IOTOK_CHARLIT); ADD_TOKEN_CHAR (moo, c); return 0; } #define CHECK_BYTE_RANGE_FOR_BYTE_ARRAY(x) do { if ((x) > 0xFF) byte_range_error = 1; } while(0) static int get_strlit (moo_t* moo, int byte_only) { /* * string-literal := single-quote string-character* single-quote * string-character := normal-character | (single-quote single-quote) * single-quote := "'" * normal-character := character-except-single-quote */ moo_ooci_t oc, c; int byte_range_error = 0; oc = moo->c->lxc.c; /* opening quote */ SET_TOKEN_TYPE (moo, MOO_IOTOK_STRLIT); GET_CHAR_TO (moo, c); if (c != oc) { do { do { in_strlit: if (byte_only) CHECK_BYTE_RANGE_FOR_BYTE_ARRAY (c); ADD_TOKEN_CHAR (moo, c); GET_CHAR_TO (moo, c); if (c == MOO_OOCI_EOF) { /* string not closed */ moo_setsynerr (moo, MOO_SYNERR_STRNC, TOKEN_LOC(moo) /*&moo->c->lxc.l*/, MOO_NULL); return -1; } } while (c != oc); /* 'c' must be a single quote at this point*/ GET_CHAR_TO (moo, c); } while (c == oc); /* if the next character is a single quote, it becomes a literal single quote character. */ } else { GET_CHAR_TO (moo, c); if (c == oc) goto in_strlit; } unget_char (moo, &moo->c->lxc); if (byte_range_error) { moo_setsynerrbfmt (moo, MOO_SYNERR_BYTERANGE, TOKEN_LOC(moo), TOKEN_NAME(moo), "non-byte in byte array literal"); return -1; } return 0; } static int get_string (moo_t* moo, moo_ooch_t end_char, moo_ooch_t esc_char, int byte_only, int regex, moo_oow_t preescaped) { moo_ooci_t c, c_acc = 0; moo_oow_t escaped = preescaped; moo_oow_t digit_count = 0; int byte_range_error = 0; SET_TOKEN_TYPE (moo, MOO_IOTOK_STRLIT); while (1) { GET_CHAR_TO (moo, c); if (c == MOO_OOCI_EOF) { moo_setsynerr (moo, MOO_SYNERR_STRNC, TOKEN_LOC(moo) /*&moo->c->lxc.l*/, MOO_NULL); return -1; } if (escaped == 3) { if (c >= '0' && c <= '7') { c_acc = c_acc * 8 + c - '0'; digit_count++; if (digit_count >= escaped) { /* should i limit the max to 0xFF/0377 regardless of byte_only? * if (c_acc > 0377) c_acc = 0377;*/ if (byte_only) CHECK_BYTE_RANGE_FOR_BYTE_ARRAY (c_acc); ADD_TOKEN_CHAR (moo, c_acc); escaped = 0; } continue; } /* octal notation with only 1 digit following 0. */ MOO_ASSERT (moo, c_acc < 8); /* it can't be bigger than 7. no need to byte check either */ /*if (byte_only) CHECK_BYTE_RANGE_FOR_BYTE_ARRAY (c_acc);*/ ADD_TOKEN_CHAR (moo, c_acc); escaped = 0; } else if (escaped == 2 || escaped == 4 || escaped == 8) { if (c >= '0' && c <= '9') { c_acc = c_acc * 16 + c - '0'; digit_count++; if (digit_count >= escaped) { if (byte_only) CHECK_BYTE_RANGE_FOR_BYTE_ARRAY (c_acc); ADD_TOKEN_CHAR (moo, c_acc); escaped = 0; } continue; } else if (c >= 'A' && c <= 'F') { c_acc = c_acc * 16 + c - 'A' + 10; digit_count++; if (digit_count >= escaped) { if (byte_only) CHECK_BYTE_RANGE_FOR_BYTE_ARRAY (c_acc); ADD_TOKEN_CHAR (moo, c_acc); escaped = 0; } continue; } else if (c >= 'a' && c <= 'f') { c_acc = c_acc * 16 + c - 'a' + 10; digit_count++; if (digit_count >= escaped) { if (byte_only) CHECK_BYTE_RANGE_FOR_BYTE_ARRAY (c_acc); ADD_TOKEN_CHAR (moo, c_acc); escaped = 0; } continue; } else { /* \x, \u, \U not followed by a hexadecimal digit */ if (digit_count == 0) { static moo_ooch_t esc_char_tab[] = { '\0', '\0', 'x', '\0', 'u', '\0', '\0', '\0', 'U' }; ADD_TOKEN_CHAR (moo, esc_char_tab[escaped]); } else { if (byte_only) CHECK_BYTE_RANGE_FOR_BYTE_ARRAY (c_acc); ADD_TOKEN_CHAR (moo, c_acc); } escaped = 0; } } if (escaped == 0 && c == end_char) { /* terminating quote */ break; } if (escaped == 0 && c == esc_char) { escaped = 1; continue; } if (escaped == 1) { if (c == 'n') c = '\n'; else if (c == 'r') c = '\r'; else if (c == 't') c = '\t'; else if (c == 'f') c = '\f'; else if (c == 'b') c = '\b'; else if (c == 'v') c = '\v'; else if (c == 'a') c = '\a'; else if (c >= '0' && c <= '7' && !regex) { /* i don't support the octal notation for a regular expression. * it conflicts with the backreference notation between \1 and \7 inclusive. */ escaped = 3; digit_count = 1; c_acc = c - '0'; continue; } else if (c == 'x') { escaped = 2; digit_count = 0; c_acc = 0; continue; } else if (!byte_only && c == 'u' && MOO_SIZEOF(moo_ooch_t) >= 2) { escaped = 4; digit_count = 0; c_acc = 0; continue; } else if (!byte_only && c == 'U' && MOO_SIZEOF(moo_ooch_t) >= 4) { escaped = 8; digit_count = 0; c_acc = 0; continue; } else if (regex) { /* if the following character doesn't compose a proper * escape sequence, keep the escape character. * an unhandled escape sequence can be handled * outside this function since the escape character * is preserved.*/ ADD_TOKEN_CHAR (moo, esc_char); } escaped = 0; } if (byte_only) CHECK_BYTE_RANGE_FOR_BYTE_ARRAY (c); ADD_TOKEN_CHAR (moo, c); } if (byte_range_error) { moo_setsynerrbfmt (moo, MOO_SYNERR_BYTERANGE, TOKEN_LOC(moo), TOKEN_NAME(moo), "non-byte in byte array literal"); return -1; } return 0; } static int get_binsel (moo_t* moo) { /* * binary-selector := binary-selector-character+ */ moo_ooci_t oc; oc = moo->c->lxc.c; ADD_TOKEN_CHAR (moo, oc); GET_CHAR (moo); /* special case if a minus is followed by a digit immediately */ if ((oc == '-' || oc == '+') && is_digitchar(moo->c->lxc.c)) return get_numlit (moo, 1); #if 1 /* up to 2 characters only */ if (is_binselchar (moo->c->lxc.c)) { ADD_TOKEN_CHAR (moo, moo->c->lxc.c); } else { unget_char (moo, &moo->c->lxc); } #else /* or up to any occurrences */ while (is_binselchar(moo->c->lxc.c)) { ADD_TOKEN_CHAR (moo, c); GET_CHAR (moo); } unget_char (moo, &moo->c->lxc); #endif SET_TOKEN_TYPE (moo, MOO_IOTOK_BINSEL); return 0; } static int get_token (moo_t* moo) { moo_ooci_t c; int n; retry: GET_CHAR (moo); do { /* skip spaces */ while (is_spacechar(moo->c->lxc.c)) GET_CHAR (moo); /* the first character after the last space is in moo->c->lxc */ if ((n = skip_comment(moo)) <= -1) return -1; } while (n >= 1); /* clear the token name, reset its location */ SET_TOKEN_TYPE (moo, MOO_IOTOK_EOF); /* is it correct? */ CLEAR_TOKEN_NAME (moo); moo->c->tok.loc = moo->c->lxc.l; /* remember token location */ c = moo->c->lxc.c; switch (c) { case MOO_OOCI_EOF: { int n; n = end_include (moo); if (n <= -1) return -1; if (n >= 1) goto retry; SET_TOKEN_TYPE (moo, MOO_IOTOK_EOF); ADD_TOKEN_STR(moo, vocas[VOCA_EOF].str, vocas[VOCA_EOF].len); break; } case '$': /* character literal */ GET_CHAR (moo); if (get_charlit(moo) <= -1) return -1; break; case '\'': /* string literal */ if (get_strlit(moo, 0) <= -1) return -1; break; case ':': SET_TOKEN_TYPE (moo, MOO_IOTOK_COLON); ADD_TOKEN_CHAR (moo, c); GET_CHAR_TO (moo, c); if (c == '=') { SET_TOKEN_TYPE (moo, MOO_IOTOK_ASSIGN); ADD_TOKEN_CHAR (moo, c); } else { unget_char (moo, &moo->c->lxc); } break; case '^': SET_TOKEN_TYPE (moo, MOO_IOTOK_RETURN); ADD_TOKEN_CHAR (moo, c); GET_CHAR_TO (moo, c); if (c == '^') { /* ^^ */ TOKEN_TYPE(moo) = MOO_IOTOK_LOCAL_RETURN; ADD_TOKEN_CHAR (moo, c); } else { unget_char (moo, &moo->c->lxc); } break; case '{': /* extension */ SET_TOKEN_TYPE (moo, MOO_IOTOK_LBRACE); goto single_char_token; case '}': /* extension */ SET_TOKEN_TYPE (moo, MOO_IOTOK_RBRACE); goto single_char_token; case '[': SET_TOKEN_TYPE (moo, MOO_IOTOK_LBRACK); goto single_char_token; case ']': SET_TOKEN_TYPE (moo, MOO_IOTOK_RBRACK); goto single_char_token; case '(': SET_TOKEN_TYPE (moo, MOO_IOTOK_LPAREN); goto single_char_token; case ')': SET_TOKEN_TYPE (moo, MOO_IOTOK_RPAREN); goto single_char_token; case '.': SET_TOKEN_TYPE (moo, MOO_IOTOK_PERIOD); goto single_char_token; case ',': SET_TOKEN_TYPE (moo, MOO_IOTOK_COMMA); goto single_char_token; case ';': SET_TOKEN_TYPE (moo, MOO_IOTOK_SEMICOLON); goto single_char_token; case '#': ADD_TOKEN_CHAR (moo, c); GET_CHAR_TO (moo, c); switch (c) { case MOO_OOCI_EOF: moo_setsynerr (moo, MOO_SYNERR_HLTNT, LEXER_LOC(moo), MOO_NULL); return -1; case '#': SET_TOKEN_TYPE (moo, MOO_IOTOK_DHASH); ADD_TOKEN_CHAR (moo, c); GET_CHAR_TO (moo, c); if (c == '(') { /* ##( - array expression */ ADD_TOKEN_CHAR (moo, c); SET_TOKEN_TYPE (moo, MOO_IOTOK_DHASHPAREN); } else if (c == '[') { /* ##[ - byte array expression */ ADD_TOKEN_CHAR (moo, c); SET_TOKEN_TYPE (moo, MOO_IOTOK_DHASHBRACK); } else if (c == '{') { /* ##{ - dictionary expression */ ADD_TOKEN_CHAR (moo, c); SET_TOKEN_TYPE (moo, MOO_IOTOK_DHASHBRACE); } else { /* NOTE the double hashes not followed by (, [, or { is * meaningless at this moment. however, i return * it as a token so that the compiler anyway * will fail eventually */ unget_char (moo, &moo->c->lxc); } break; case '(': /* #( - array literal */ ADD_TOKEN_CHAR (moo, c); SET_TOKEN_TYPE (moo, MOO_IOTOK_HASHPAREN); break; case '[': /* #[ - byte array literal */ ADD_TOKEN_CHAR (moo, c); SET_TOKEN_TYPE (moo, MOO_IOTOK_HASHBRACK); break; case '{': /* #[ - dictionary literal */ ADD_TOKEN_CHAR (moo, c); SET_TOKEN_TYPE (moo, MOO_IOTOK_HASHBRACE); break; case '\'': /* #'XXXX' - quoted symbol literal */ if (get_strlit(moo, 0) <= -1) return -1; /* reuse the string literal tokenizer */ SET_TOKEN_TYPE (moo, MOO_IOTOK_SYMLIT); /* change the symbol type to symbol */ break; case '"': /* #"XXXX" - quoted symbol literal with C-style escape sequences. * if MOO_PRAGMA_QC is set, this part should never be reached */ MOO_ASSERT (moo, !(moo->c->pragma_flags & MOO_PRAGMA_QC)); if (get_string(moo, '"', '\\', 0, 0, 0) <= -1) return -1; SET_TOKEN_TYPE (moo, MOO_IOTOK_SYMLIT); /* change the symbol type to symbol */ break; case '\\': ADD_TOKEN_CHAR (moo, c); GET_CHAR_TO (moo, c); if (c == 'E' || c == 'e') { /* #\eNNN - error literal - #\e0, #\e1234, etc */ SET_TOKEN_TYPE(moo, MOO_IOTOK_ERRLIT); ADD_TOKEN_CHAR (moo, c); GET_CHAR_TO (moo, c); if (!is_digitchar(c)) { ADD_TOKEN_CHAR (moo, c); /* to include it to the error messsage */ moo_setsynerr (moo, MOO_SYNERR_ERRLITINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } do { ADD_TOKEN_CHAR (moo, c); GET_CHAR_TO (moo, c); } while (is_digitchar(c) || c == '_'); } else if (c == 'P' || c == 'p') { /* #\pXXX - smptr literal - #\p0, #\p100, etc */ SET_TOKEN_TYPE(moo, MOO_IOTOK_SMPTRLIT); ADD_TOKEN_CHAR (moo, c); GET_CHAR_TO (moo, c); if (!is_xdigitchar(c)) { ADD_TOKEN_CHAR (moo, c); /* to include it to the error messsage */ moo_setsynerr (moo, MOO_SYNERR_SMPTRLITINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } do { ADD_TOKEN_CHAR (moo, c); GET_CHAR_TO (moo, c); } while (is_xdigitchar(c) || c == '_'); } else { ADD_TOKEN_CHAR (moo, c); /* to include it to the error messsage */ moo_setsynerr (moo, MOO_SYNERR_HBSLPINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } unget_char (moo, &moo->c->lxc); break; default: /* symbol-literal := "#" symbol-body * symbol-body := identifier | keyword+ | binary-selector | string-literal */ /* unquoted symbol literal */ if (is_binselchar(c)) { do { ADD_TOKEN_CHAR (moo, c); GET_CHAR_TO (moo, c); } while (is_binselchar(c)); unget_char (moo, &moo->c->lxc); } else if (is_leadidentchar(c)) { do { ADD_TOKEN_CHAR (moo, c); GET_CHAR_TO (moo, c); } while (is_identchar(c)); if (c == ':') { /* keyword symbol - e.g. #ifTrue:ifFalse: */ read_more_word: ADD_TOKEN_CHAR (moo, c); GET_CHAR_TO (moo, c); if (is_leadidentchar(c)) { do { ADD_TOKEN_CHAR (moo, c); GET_CHAR_TO (moo, c); } while (is_identchar(c)); if (c == ':') goto read_more_word; else { /* if a colon is found in the middle of a symbol, * the last charater is expected to be a colon as well. * but, the last character is not a colon */ moo_setsynerr (moo, MOO_SYNERR_COLON, LEXER_LOC(moo), MOO_NULL); return -1; } } else { unget_char (moo, &moo->c->lxc); } } else if (c == '.') { /* dotted symbol e.g. #Planet.Earth.Object */ moo_iolxc_t period; period = moo->c->lxc; read_more_seg: GET_CHAR_TO (moo, c); if (is_leadidentchar(c)) { ADD_TOKEN_CHAR (moo, '.'); do { ADD_TOKEN_CHAR (moo, c); GET_CHAR_TO (moo, c); } while (is_identchar(c)); if (c == '.') goto read_more_seg; else unget_char (moo, &moo->c->lxc); } else { unget_char (moo, &moo->c->lxc); unget_char (moo, &period); } } else { unget_char (moo, &moo->c->lxc); } } else { moo_setsynerr (moo, MOO_SYNERR_HLTNT, LEXER_LOC(moo), MOO_NULL); return -1; } SET_TOKEN_TYPE (moo, MOO_IOTOK_SYMLIT); break; } break; case '"': /* if MOO_PRAGMA_QC is set, this part should never be reached */ MOO_ASSERT (moo, !(moo->c->pragma_flags & MOO_PRAGMA_QC)); if (get_string(moo, '"', '\\', 0, 0, 0) <= -1) return -1; break; case 'C': /* a character with a C-style escape sequence */ case 'B': /* byte array in a string like notation */ { moo_ooci_t saved_c = c; GET_CHAR_TO (moo, c); if (c == '\'') { /*GET_CHAR (moo);*/ if (get_strlit(moo, (saved_c == 'B')) <= -1) return -1; if (saved_c == 'C') { if (TOKEN_NAME_LEN(moo) != 1) { moo_setsynerr (moo, MOO_SYNERR_CHARLITINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } SET_TOKEN_TYPE (moo, MOO_IOTOK_CHARLIT); } else if (saved_c == 'B') { SET_TOKEN_TYPE (moo, MOO_IOTOK_BYTEARRAYLIT); } } else if (c == '\"') { /*GET_CHAR (moo);*/ if (get_string(moo, '\"', '\\', (saved_c == 'B'), 0, 0) <= -1) return -1; if (saved_c == 'C') { if (TOKEN_NAME_LEN(moo) != 1) { moo_setsynerr (moo, MOO_SYNERR_CHARLITINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } SET_TOKEN_TYPE (moo, MOO_IOTOK_CHARLIT); } else if (saved_c == 'B') { SET_TOKEN_TYPE (moo, MOO_IOTOK_BYTEARRAYLIT); } } else { if (get_ident(moo, saved_c) <= -1) return -1; } break; } /* case 'B': TODO: byte string with a c-style escape sequence? */ /* case 'R': TODO: regular expression? GET_CHAR_TO (moo, c); if (c == '\'') { GET_CHAR (moo); if (get_rexlit(moo) <= -1) return -1; } else { if (get_ident(moo, 'R') <= -1) return -1; } break; */ default: if (is_leadidentchar(c)) { if (get_ident(moo, MOO_OOCI_EOF) <= -1) return -1; } else if (is_digitchar(c)) { if (get_numlit(moo, 0) <= -1) return -1; } else if (is_binselchar(c)) { /* binary selector */ if (get_binsel(moo) <= -1) return -1; } else { moo->c->ilchr = (moo_ooch_t)c; moo_setsynerr (moo, MOO_SYNERR_ILCHR, LEXER_LOC(moo), &moo->c->ilchr_ucs); return -1; } break; single_char_token: ADD_TOKEN_CHAR (moo, c); break; } #if defined(MOO_DEBUG_LEXER) MOO_DEBUG3 (moo, "TOKEN: [%.*js] %d\n", (moo_ooi_t)moo->c->tok.name.len, moo->c->tok.name.ptr, (int)moo->c->tok.type); #endif return 0; } void moo_clearcionames (moo_t* moo) { moo_iolink_t* cur; MOO_ASSERT (moo, moo->c != MOO_NULL); while (moo->c->io_names) { cur = moo->c->io_names; moo->c->io_names = cur->link; moo_freemem (moo, cur); } } const moo_ooch_t* moo_addcioname (moo_t* moo, const moo_oocs_t* name) { moo_iolink_t* link; moo_ooch_t* ptr; link = (moo_iolink_t*)moo_callocmem(moo, MOO_SIZEOF(*link) + MOO_SIZEOF(moo_ooch_t) * (name->len + 1)); if (!link) return MOO_NULL; ptr = (moo_ooch_t*)(link + 1); moo_copy_oochars (ptr, name->ptr, name->len); ptr[name->len] = '\0'; link->link = moo->c->io_names; moo->c->io_names = link; return ptr; } static int begin_include (moo_t* moo) { moo_ioarg_t* arg; const moo_ooch_t* io_name; io_name = moo_addcioname(moo, TOKEN_NAME(moo)); if (!io_name) return -1; arg = (moo_ioarg_t*)moo_callocmem(moo, MOO_SIZEOF(*arg)); if (!arg) goto oops; arg->name = io_name; arg->line = 1; arg->colm = 1; /*arg->nl = '\0';*/ arg->includer = moo->c->curinp; if (moo->c->impl(moo, MOO_IO_OPEN, arg) <= -1) { moo_setsynerr (moo, MOO_SYNERR_INCLUDE, TOKEN_LOC(moo), TOKEN_NAME(moo)); goto oops; } GET_TOKEN (moo); if (TOKEN_TYPE(moo) != MOO_IOTOK_PERIOD) { /* check if a period is following the includee name */ moo_setsynerr (moo, MOO_SYNERR_PERIOD, TOKEN_LOC(moo), TOKEN_NAME(moo)); goto oops; } /* switch to the includee's stream */ moo->c->curinp = arg; /* moo->c->depth.incl++; */ /* read in the first character in the included file. * so the next call to get_token() sees the character read * from this file. */ if (get_token(moo) <= -1) { end_include (moo); /* i don't jump to oops since i've called * end_include() which frees moo->c->curinp/arg */ return -1; } return 0; oops: if (arg) moo_freemem (moo, arg); return -1; } static int end_include (moo_t* moo) { int x; moo_ioarg_t* cur; if (moo->c->curinp == &moo->c->arg) return 0; /* no include */ /* if it is an included file, close it and * retry to read a character from an outer file */ x = moo->c->impl (moo, MOO_IO_CLOSE, moo->c->curinp); /* if closing has failed, still destroy the * sio structure first as normal and return * the failure below. this way, the caller * does not call MOO_IO_CLOSE on * moo->c->curinp again. */ cur = moo->c->curinp; moo->c->curinp = moo->c->curinp->includer; MOO_ASSERT (moo, cur->name != MOO_NULL); moo_freemem (moo, cur); /* moo->parse.depth.incl--; */ if (x != 0) { /* the failure mentioned above is returned here */ return -1; } moo->c->lxc = moo->c->curinp->lxc; return 1; /* ended the included file successfully */ } /* --------------------------------------------------------------------- * Literal * --------------------------------------------------------------------- */ static MOO_INLINE int add_literal (moo_t* moo, moo_oop_t lit, moo_oow_t* index) { /* * this removes redundancy of symbols, characters, and small integers. * more complex redundacy check may be done somewhere else like * in add_string_literal(). */ moo_method_data_t* md = get_cunit_method_data(moo); return add_oop_to_oopbuf_nodup(moo, &md->literals, lit, index); } static int add_string_literal (moo_t* moo, const moo_oocs_t* str, moo_oow_t* index) { moo_method_data_t* md = get_cunit_method_data(moo); moo_oop_t lit; moo_oow_t i; for (i = 0; i < md->literals.count; i++) { lit = md->literals.ptr[i]; if (MOO_CLASSOF(moo, lit) == moo->_string && MOO_OBJ_GET_SIZE(lit) == str->len && moo_equal_oochars(MOO_OBJ_GET_CHAR_SLOT(lit), str->ptr, str->len)) { *index = i; return 0; } } lit = moo_instantiate(moo, moo->_string, str->ptr, str->len); if (!lit) return -1; MOO_OBJ_SET_FLAGS_RDONLY (lit, 1); return add_literal(moo, lit, index); } static int add_symbol_literal (moo_t* moo, const moo_oocs_t* str, moo_oow_t offset, moo_oow_t* index) { moo_oop_t tmp; tmp = moo_makesymbol(moo, str->ptr + offset, str->len - offset); if (!tmp) return -1; MOO_OBJ_SET_FLAGS_RDONLY (tmp, 1); return add_literal(moo, tmp, index); } static int add_byte_array_literal (moo_t* moo, const moo_oocs_t* str, moo_oow_t* index) { /* see read_byte_array_literal for comparision */ moo_oop_t tmp; moo_oow_t i; tmp = moo_instantiate(moo, moo->_byte_array, MOO_NULL, str->len); if (!tmp) return -1; for (i = 0; i < str->len; i++) MOO_OBJ_SET_BYTE_VAL(tmp, i, str->ptr[i]); MOO_OBJ_SET_FLAGS_RDONLY (tmp, 1); return add_literal(moo, tmp, index); } /* --------------------------------------------------------------------- * Byte-Code Manipulation Functions * --------------------------------------------------------------------- */ static MOO_INLINE int emit_byte_instruction (moo_t* moo, moo_oob_t code, const moo_ioloc_t* srcloc) { moo_method_data_t* md = get_cunit_method_data(moo); /* the context object has the ip field. it should be representable * in a small integer. for simplicity, limit the total byte code length * to fit in a small integer. because 'ip' points to the next instruction * to execute, he upper bound should be (max - 1) so that i stays * at the max when incremented */ if (md->code.len == MOO_SMOOI_MAX - 1) { moo_seterrnum (moo, MOO_EBCFULL); /* byte code too big */ return -1; } if (md->code.len >= md->code.capa) { moo_oob_t* tmp; moo_oow_t* tmp2; moo_oow_t newcapa; newcapa = MOO_ALIGN (md->code.len + 1, CODE_BUFFER_ALIGN); tmp = (moo_oob_t*)moo_reallocmem(moo, md->code.ptr, newcapa * MOO_SIZEOF(*tmp)); if (!tmp) return -1; tmp2 = (moo_oow_t*)moo_reallocmem(moo, md->code.locptr, newcapa * MOO_SIZEOF(*tmp2)); if (!tmp) { moo_freemem (moo, tmp); return -1; } md->code.capa = newcapa; md->code.ptr = tmp; md->code.locptr = tmp2; } md->code.ptr[md->code.len] = code; if (srcloc) { if (srcloc->file == md->start_loc.file && srcloc->line >= md->start_loc.line) { /*md->code.locptr[md->code.len] = srcloc->line - md->start_loc.line; // relative within the method? */ md->code.locptr[md->code.len] = srcloc->line; } else { /* i can't record the distance as the method body is not in the same file */ /* TODO: warning, error handling? */ md->code.locptr[md->code.len] = 0; } } md->code.len++; return 0; } static int emit_single_param_instruction (moo_t* moo, int cmd, moo_oow_t param_1, const moo_ioloc_t* srcloc) { moo_oob_t bc; switch (cmd) { case BCODE_PUSH_INSTVAR_0: case BCODE_STORE_INTO_INSTVAR_0: case BCODE_POP_INTO_INSTVAR_0: case BCODE_PUSH_TEMPVAR_0: case BCODE_STORE_INTO_TEMPVAR_0: case BCODE_POP_INTO_TEMPVAR_0: case BCODE_PUSH_LITERAL_0: if (param_1 < 8) { /* low 3 bits to hold the parameter */ bc = (moo_oob_t)(cmd & 0xF8) | (moo_oob_t)param_1; goto write_short; } else { /* convert the code to a long version */ bc = cmd | 0x80; goto write_long; } case BCODE_PUSH_OBJECT_0: case BCODE_STORE_INTO_OBJECT_0: case BCODE_POP_INTO_OBJECT_0: if (param_1 < 4) { /* low 2 bits to hold the parameter */ bc = (moo_oob_t)(cmd & 0xFC) | (moo_oob_t)param_1; goto write_short; } else { /* convert the instruction to a long version (_X) */ bc = cmd | 0x80; goto write_long; } case BCODE_JUMP_BACKWARD: case BCODE_JMPOP_BACKWARD_IF_FALSE: case BCODE_JMPOP_BACKWARD_IF_TRUE: case BCODE_JUMP_FORWARD: case BCODE_JUMP_FORWARD_IF_TRUE: case BCODE_JUMP_FORWARD_IF_FALSE: case BCODE_JMPOP_FORWARD_IF_FALSE: case BCODE_JMPOP_FORWARD_IF_TRUE: if (param_1 > MAX_CODE_JUMP) { cmd = cmd + 1; /* convert to a JUMP2 instruction */ param_1 = param_1 - MAX_CODE_JUMP; } /* fall thru */ case BCODE_JUMP2_FORWARD: case BCODE_JUMP2_FORWARD_IF_TRUE: case BCODE_JUMP2_FORWARD_IF_FALSE: case BCODE_JUMP2_BACKWARD: case BCODE_JMPOP2_BACKWARD_IF_FALSE: case BCODE_JMPOP2_BACKWARD_IF_TRUE: case BCODE_JMPOP2_FORWARD_IF_FALSE: case BCODE_JMPOP2_FORWARD_IF_TRUE: case BCODE_PUSH_INTLIT: case BCODE_PUSH_NEGINTLIT: case BCODE_PUSH_CHARLIT: case BCODE_MAKE_DICTIONARY: case BCODE_MAKE_ARRAY: case BCODE_POP_INTO_ARRAY: case BCODE_MAKE_BYTEARRAY: case BCODE_POP_INTO_BYTEARRAY: bc = cmd; goto write_long; } MOO_DEBUG1 (moo, "Invalid single param instruction opcode %d\n", (int)cmd); moo_seterrnum (moo, MOO_EINVAL); return -1; write_short: if (emit_byte_instruction(moo, bc, srcloc) <= -1) return -1; return 0; write_long: if (param_1 > MAX_CODE_PARAM) { moo_seterrnum (moo, MOO_ERANGE); return -1; } #if (MOO_BCODE_LONG_PARAM_SIZE == 2) if (emit_byte_instruction(moo, bc, srcloc) <= -1 || emit_byte_instruction(moo, (param_1 >> 8) & 0xFF, srcloc) <= -1 || emit_byte_instruction(moo, param_1 & 0xFF, srcloc) <= -1) return -1; #else if (emit_byte_instruction(moo, bc, srcloc) <= -1 || emit_byte_instruction(moo, param_1, srcloc) <= -1) return -1; #endif return 0; } static int emit_double_param_instruction (moo_t* moo, int cmd, moo_oow_t param_1, moo_oow_t param_2, const moo_ioloc_t* srcloc) { moo_oob_t bc; switch (cmd) { case BCODE_STORE_INTO_CTXTEMPVAR_0: case BCODE_POP_INTO_CTXTEMPVAR_0: case BCODE_PUSH_CTXTEMPVAR_0: case BCODE_PUSH_OBJVAR_0: case BCODE_STORE_INTO_OBJVAR_0: case BCODE_POP_INTO_OBJVAR_0: case BCODE_SEND_MESSAGE_0: case BCODE_SEND_MESSAGE_TO_SUPER_0: if (param_1 < 4 && param_2 < 0xFF) { /* low 2 bits of the instruction code is the first parameter */ bc = (moo_oob_t)(cmd & 0xFC) | (moo_oob_t)param_1; goto write_short; } else { /* convert the code to a long version */ bc = cmd | 0x80; goto write_long; } case BCODE_MAKE_BLOCK: bc = cmd; goto write_long; } MOO_DEBUG1 (moo, "Invalid double param instruction opcode %d\n", (int)cmd); moo_seterrnum (moo, MOO_EINVAL); return -1; write_short: if (emit_byte_instruction(moo, bc, srcloc) <= -1 || emit_byte_instruction(moo, param_2, srcloc) <= -1) return -1; return 0; write_long: if (param_1 > MAX_CODE_PARAM || param_2 > MAX_CODE_PARAM) { moo_seterrnum (moo, MOO_ERANGE); return -1; } #if (MOO_BCODE_LONG_PARAM_SIZE == 2) if (emit_byte_instruction(moo, bc, srcloc) <= -1 || emit_byte_instruction(moo, (param_1 >> 8) & 0xFF, srcloc) <= -1 || emit_byte_instruction(moo, param_1 & 0xFF, srcloc) <= -1 || emit_byte_instruction(moo, (param_2 >> 8) & 0xFF, srcloc) <= -1 || emit_byte_instruction(moo, param_2 & 0xFF, srcloc) <= -1) return -1; #else if (emit_byte_instruction(moo, bc, srcloc) <= -1 || emit_byte_instruction(moo, param_1, srcloc) <= -1 || emit_byte_instruction(moo, param_2, srcloc) <= -1) return -1; #endif return 0; } static int emit_push_smooi_literal (moo_t* moo, moo_ooi_t i, const moo_ioloc_t* srcloc) { moo_oow_t index; switch (i) { case -1: return emit_byte_instruction(moo, BCODE_PUSH_NEGONE, srcloc); case 0: return emit_byte_instruction(moo, BCODE_PUSH_ZERO, srcloc); case 1: return emit_byte_instruction(moo, BCODE_PUSH_ONE, srcloc); case 2: return emit_byte_instruction(moo, BCODE_PUSH_TWO, srcloc); } if (i >= 0 && i <= MAX_CODE_PARAM) { return emit_single_param_instruction(moo, BCODE_PUSH_INTLIT, i, srcloc); } else if (i < 0 && i >= -(moo_ooi_t)MAX_CODE_PARAM) { return emit_single_param_instruction(moo, BCODE_PUSH_NEGINTLIT, -i, srcloc); } if (add_literal(moo, MOO_SMOOI_TO_OOP(i), &index) <= -1 || emit_single_param_instruction(moo, BCODE_PUSH_LITERAL_0, index, srcloc) <= -1) return -1; return 0; } static int emit_push_character_literal (moo_t* moo, moo_ooch_t ch, const moo_ioloc_t* srcloc) { moo_oow_t index; if (ch >= 0 && ch <= MAX_CODE_PARAM) { return emit_single_param_instruction(moo, BCODE_PUSH_CHARLIT, ch, srcloc); } if (add_literal(moo, MOO_CHAR_TO_OOP(ch), &index) <= -1 || emit_single_param_instruction(moo, BCODE_PUSH_LITERAL_0, index, srcloc) <= -1) return -1; return 0; } static MOO_INLINE int emit_backward_jump_instruction (moo_t* moo, int cmd, moo_oow_t offset, const moo_ioloc_t* srcloc) { moo_oow_t adj; MOO_ASSERT (moo, cmd == BCODE_JUMP_BACKWARD || cmd == BCODE_JMPOP_BACKWARD_IF_FALSE || cmd == BCODE_JMPOP_BACKWARD_IF_TRUE); adj = MOO_BCODE_LONG_PARAM_SIZE + 1; /* adjust by the size of instruction */ return emit_single_param_instruction(moo, cmd, offset + adj, srcloc); } static int patch_forward_jump_instruction (moo_t* moo, moo_oow_t jip, moo_oow_t jt) { moo_method_data_t* md = get_cunit_method_data(moo); moo_oow_t code_size; moo_oow_t jump_offset; MOO_ASSERT (moo, md->code.ptr[jip] == BCODE_JUMP_FORWARD || md->code.ptr[jip] == BCODE_JUMP_FORWARD_IF_FALSE || md->code.ptr[jip] == BCODE_JUMP_FORWARD_IF_TRUE || md->code.ptr[jip] == BCODE_JMPOP_FORWARD_IF_FALSE || md->code.ptr[jip] == BCODE_JMPOP_FORWARD_IF_TRUE); if (jt <= jip) { /* backward jump */ /* it's also backward jump if jt == jip. when the jump instruction is executed, * the intruction pointer advances. so it should jump backward to get back to * the same position */ MOO_STATIC_ASSERT (BCODE_JUMP_FORWARD + 10 == BCODE_JUMP_BACKWARD); MOO_STATIC_ASSERT (BCODE_JUMP_FORWARD_IF_TRUE + 10 == BCODE_JUMP_BACKWARD_IF_TRUE); MOO_STATIC_ASSERT (BCODE_JUMP_FORWARD_IF_FALSE + 10 == BCODE_JUMP_BACKWARD_IF_FALSE); MOO_STATIC_ASSERT (BCODE_JMPOP_FORWARD_IF_TRUE + 10 == BCODE_JMPOP_BACKWARD_IF_TRUE); MOO_STATIC_ASSERT (BCODE_JMPOP_FORWARD_IF_FALSE + 10 == BCODE_JMPOP_BACKWARD_IF_FALSE); md->code.ptr[jip] += 10; /* switch to the backward jump instruction */ code_size = jip - jt + (MOO_BCODE_LONG_PARAM_SIZE + 1); } else { /* jip - jump instruction pointer, jt - jump target * * * when this jump instruction is executed, the instruction pointer advances * to the next instruction. so the actual jump size gets offset by the size * of this jump instruction. MOO_BCODE_LONG_PARAM_SIZE + 1 is the size of * the long JUMP_FORWARD instruction */ code_size = jt - jip - (MOO_BCODE_LONG_PARAM_SIZE + 1); } if (code_size > MAX_CODE_JUMP * 2) { moo_seterrnum (moo, MOO_ERANGE); return -1; } if (code_size > MAX_CODE_JUMP) { /* switch to JUMP2 instruction to allow a bigger jump offset. * up to twice MAX_CODE_JUMP only */ md->code.ptr[jip]++; /* switch to the JUMP2 instruction */ jump_offset = code_size - MAX_CODE_JUMP; } else { jump_offset = code_size; } #if (MOO_BCODE_LONG_PARAM_SIZE == 2) md->code.ptr[jip + 1] = jump_offset >> 8; md->code.ptr[jip + 2] = jump_offset & 0xFF; #else md->code.ptr[jip + 1] = jump_offset; #endif return 0; } static int push_loop (moo_t* moo, moo_loop_type_t type, moo_oow_t startpos) { moo_loop_t* loop; loop = (moo_loop_t*)moo_callocmem(moo, MOO_SIZEOF(*loop)); if (!loop) return -1; init_oow_pool (moo, &loop->break_ip_pool); init_oow_pool (moo, &loop->continue_ip_pool); loop->type = type; loop->startpos = startpos; /* link the new loop to the loop chain of the current method being compiled */ MOO_ASSERT (moo, moo->c->cunit && moo->c->cunit->cunit_type == MOO_CUNIT_CLASS); loop->next = ((moo_cunit_class_t*)moo->c->cunit)->mth.loop; ((moo_cunit_class_t*)moo->c->cunit)->mth.loop = loop; return 0; } static int update_loop_jumps (moo_t* moo, moo_oow_pool_t* pool, moo_oow_t jt) { /* patch the jump instructions emitted for 'break' or 'continue' */ moo_oow_pool_chunk_t* chunk; moo_oow_t i, j; for (chunk = pool->head, i = 0; chunk; chunk = chunk->next) { for (j = 0; j < MOO_COUNTOF(pool->static_chunk.buf) && i < pool->count; j++) { if (chunk->buf[j].v != INVALID_IP && patch_forward_jump_instruction(moo, chunk->buf[j].v, jt) <= -1) { moo_setsynerrbfmt (moo, MOO_SYNERR_INSTFLOOD, &chunk->buf[j].loc, MOO_NULL, "unable to patch conditional loop jump"); return -1; } i++; } } return 0; } static void adjust_loop_jumps_for_elimination (moo_t* moo, moo_oow_pool_t* pool, moo_oow_t start, moo_oow_t end) { /* update the jump instruction positions emitted for 'break' or 'continue' */ moo_oow_pool_chunk_t* chunk; moo_oow_t i, j; for (chunk = pool->head, i = 0; chunk; chunk = chunk->next) { for (j = 0; j < MOO_COUNTOF(pool->static_chunk.buf) && i < pool->count; j++) { if (chunk->buf[j].v != INVALID_IP) { if (chunk->buf[j].v >= start && chunk->buf[j].v <= end) { /* invalidate the instruction position */ chunk->buf[j].v = INVALID_IP; } else if (chunk->buf[j].v > end && chunk->buf[j].v < ((moo_cunit_class_t*)moo->c->cunit)->mth.code.len) { /* decrement the instruction position */ chunk->buf[j].v -= end - start + 1; } } i++; } } } static MOO_INLINE int update_loop_breaks (moo_t* moo, moo_oow_t jt) { return update_loop_jumps(moo, &((moo_cunit_class_t*)moo->c->cunit)->mth.loop->break_ip_pool, jt); } static MOO_INLINE int update_loop_continues (moo_t* moo, moo_oow_t jt) { return update_loop_jumps(moo, &((moo_cunit_class_t*)moo->c->cunit)->mth.loop->continue_ip_pool, jt); } static MOO_INLINE void adjust_all_loop_jumps_for_elimination (moo_t* moo, moo_oow_t start, moo_oow_t end) { moo_method_data_t* md = get_cunit_method_data(moo); moo_loop_t* loop; loop = md->loop; while (loop) { adjust_loop_jumps_for_elimination (moo, &loop->break_ip_pool, start, end); adjust_loop_jumps_for_elimination (moo, &loop->continue_ip_pool, start, end); loop = loop->next; } } static MOO_INLINE void adjust_all_gotos_for_elimination (moo_t* moo, moo_oow_t start, moo_oow_t end) { moo_method_data_t* md = get_cunit_method_data(moo); moo_goto_t* _goto; _goto = md->_goto; while (_goto) { if (_goto->ip != INVALID_IP) { if (_goto->ip >= start && _goto->ip <= end) { /* invalidate this entry since the goto instruction itself is getting eliminated. * i don't kill this node. the resolver must skip this node. */ _goto->ip = INVALID_IP; } else if (_goto->ip > end) { _goto->ip -= end - start + 1; } } _goto = _goto->next; } } static MOO_INLINE void adjust_all_labels_for_elimination (moo_t* moo, moo_oow_t start, moo_oow_t end) { moo_method_data_t* md = get_cunit_method_data(moo); moo_label_t* _label; _label = md->_label; while (_label) { if (_label->ip >= start && _label->ip <= end) { /* TODO: ERROR - cannot eliminate this part. caller must ensure this doesn't happen */ MOO_ASSERT (moo, _label->ip < start || _label->ip > end); } else if (_label->ip > end) { _label->ip -= end - start + 1; } _label = _label->next; } } static MOO_INLINE moo_loop_t* unlink_loop (moo_t* moo) { moo_method_data_t* md = get_cunit_method_data(moo); moo_loop_t* loop; MOO_ASSERT (moo, md->loop != MOO_NULL); loop = md->loop; md->loop = loop->next; return loop; } static MOO_INLINE void free_loop (moo_t* moo, moo_loop_t* loop) { fini_oow_pool (moo, &loop->continue_ip_pool); fini_oow_pool (moo, &loop->break_ip_pool); moo_freemem (moo, loop); } static MOO_INLINE void pop_loop (moo_t* moo) { free_loop (moo, unlink_loop(moo)); } static MOO_INLINE int inject_break_to_loop (moo_t* moo, const moo_ioloc_t* srcloc) { moo_method_data_t* md = get_cunit_method_data(moo); if (add_to_oow_pool(moo, &md->loop->break_ip_pool, md->code.len, srcloc) <= -1 || emit_single_param_instruction(moo, BCODE_JUMP_FORWARD, MAX_CODE_JUMP, srcloc) <= -1) return -1; return 0; } static MOO_INLINE int inject_continue_to_loop (moo_t* moo, const moo_ioloc_t* srcloc) { moo_method_data_t* md = get_cunit_method_data(moo); /* used for a do-while loop. jump forward because the conditional * is at the end of the do-while loop */ if (add_to_oow_pool(moo, &md->loop->continue_ip_pool, md->code.len, srcloc) <= -1 || emit_single_param_instruction(moo, BCODE_JUMP_FORWARD, MAX_CODE_JUMP, srcloc) <= -1) return -1; return 0; } static void eliminate_instructions (moo_t* moo, moo_oow_t start, moo_oow_t end) { moo_oow_t last; moo_method_data_t* md = get_cunit_method_data(moo); MOO_ASSERT (moo, md->code.len >= 1); last = md->code.len - 1; if (end >= last) { /* eliminate all instructions starting from the start index. * setting the length to the start length will achieve this */ adjust_all_loop_jumps_for_elimination (moo, start, last); adjust_all_gotos_for_elimination (moo, start, last); adjust_all_labels_for_elimination (moo, start, last); md->code.len = start; } else { moo_oow_t tail_len; /* eliminate a chunk in the middle of the instruction buffer. * some copying is required */ adjust_all_loop_jumps_for_elimination (moo, start, end); adjust_all_gotos_for_elimination (moo, start, end); adjust_all_labels_for_elimination (moo, start, end); tail_len = md->code.len - end - 1; MOO_MEMMOVE (&md->code.ptr[start], &md->code.ptr[end + 1], tail_len * MOO_SIZEOF(md->code.ptr[0])); MOO_MEMMOVE (&md->code.locptr[start], &md->code.locptr[end + 1], tail_len * MOO_SIZEOF(md->code.locptr[0])); md->code.len -= end - start + 1; } } /* --------------------------------------------------------------------- * Compiler * --------------------------------------------------------------------- */ static MOO_INLINE int set_class_fqn (moo_t* moo, moo_cunit_class_t* cc, const moo_oocs_t* name) { if (copy_string_to(moo, name, &cc->fqn, &cc->fqn_capa, 0, '\0') <= -1) return -1; cc->name = cc->fqn; return 0; } static MOO_INLINE int set_superclass_fqn (moo_t* moo, moo_cunit_class_t* cc, const moo_oocs_t* name) { if (copy_string_to(moo, name, &cc->superfqn, &cc->superfqn_capa, 0, '\0') <= -1) return -1; cc->supername = cc->superfqn; return 0; } static MOO_INLINE int set_class_modname (moo_t* moo, moo_cunit_class_t* cc, const moo_oocs_t* name) { if (copy_string_to(moo, name, &cc->modname, &cc->modname_capa, 0, '\0') <= -1) return -1; return 0; } static MOO_INLINE int set_pooldic_fqn (moo_t* moo, moo_cunit_pooldic_t* pd, const moo_oocs_t* name) { if (copy_string_to(moo, name, &pd->fqn, &pd->fqn_capa, 0, '\0') <= -1) return -1; pd->name = pd->fqn; return 0; } static MOO_INLINE int prefix_pooldic_fqn (moo_t* moo, moo_cunit_pooldic_t* pd, const moo_oocs_t* prefix) { /* implementaion goes by appending and rotation. inefficient but works. */ if (copy_string_to(moo, prefix, &pd->fqn, &pd->fqn_capa, 1, '\0') <= -1) return -1; moo_rotate_oochars (pd->fqn.ptr, pd->fqn.len, 1, prefix->len); pd->name.ptr += prefix->len; return 0; } static MOO_INLINE int set_interface_fqn (moo_t* moo, moo_cunit_interface_t* ifce, const moo_oocs_t* name) { if (copy_string_to(moo, name, &ifce->fqn, &ifce->fqn_capa, 0, '\0') <= -1) return -1; ifce->name = ifce->fqn; return 0; } static MOO_INLINE int add_class_level_variable (moo_t* moo, var_type_t var_type, const moo_oocs_t* name, const moo_ioloc_t* loc) { moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; int n; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_CLASS); n = copy_string_to(moo, name, &cc->var[var_type].str, &cc->var[var_type].str_capa, 1, ' '); if (n >= 0) { static moo_oow_t varlim[] = { MOO_MAX_NAMED_INSTVARS, /* VAR_INSTANCE */ MOO_MAX_CLASSINSTVARS, /* VAR_CLASSINST */ MOO_MAX_CLASSVARS, /* VAR_CLASS */ }; MOO_ASSERT (moo, VAR_INSTANCE == 0); MOO_ASSERT (moo, VAR_CLASSINST == 1); MOO_ASSERT (moo, VAR_CLASS == 2); MOO_ASSERT (moo, var_type >= VAR_INSTANCE && var_type <= VAR_CLASS); if (cc->var[var_type].total_count >= varlim[var_type]) { moo_setsynerrbfmt (moo, MOO_SYNERR_VARFLOOD, loc, name, "too many class-level variables"); return -1; } cc->var[var_type].count++; cc->var[var_type].total_count++; } return n; } static int set_class_level_variable_initv (moo_t* moo, var_type_t var_type, moo_oow_t var_index, moo_oop_t initv, int flags) { moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_CLASS); if (var_index >= cc->var[var_type].initv_capa) { moo_oow_t newcapa, oldcapa; /*moo_oow_t i;*/ moo_initv_t* tmp; oldcapa = cc->var[var_type].initv_capa; newcapa = MOO_ALIGN_POW2 ((var_index + 1), 32); tmp = (moo_initv_t*)moo_reallocmem(moo, cc->var[var_type].initv, newcapa * MOO_SIZEOF(*tmp)); if (!tmp) return -1; /*for (i = cc->var[var_type].initv_capa; i < newcapa; i++) tmp[i] = MOO_NULL;*/ MOO_MEMSET (&tmp[oldcapa], 0, (newcapa - oldcapa) * MOO_SIZEOF(moo_oop_t)); cc->var[var_type].initv = tmp; cc->var[var_type].initv_capa = newcapa; } if (var_index >= cc->var[var_type].initv_count) { moo_oow_t i; for (i = cc->var[var_type].initv_count; i < var_index; i++) { cc->var[var_type].initv[i].v = MOO_NULL; cc->var[var_type].initv[i].flags = 0; } cc->var[var_type].initv_count = var_index + 1; } cc->var[var_type].initv[var_index].v = initv; cc->var[var_type].initv[var_index].flags = flags; return 0; } static MOO_INLINE int add_pooldic_import (moo_t* moo, moo_cunit_class_t* cc, const moo_oocs_t* name, moo_oop_dic_t pooldic_oop) { if (add_oop_to_oopbuf(moo, &cc->pdimp.dics, (moo_oop_t)pooldic_oop) <= -1) return -1; if (copy_string_to(moo, name, &cc->pdimp.dcl, &cc->pdimp.dcl_capa, 1, ' ') <= -1) { cc->pdimp.dics.count--; /* roll back */ return -1; } return 0; } static moo_ooi_t find_class_level_variable (moo_t* moo, moo_oop_class_t self, const moo_oocs_t* name, var_info_t* var) { moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; moo_oow_t pos; moo_oop_t super; moo_oop_char_t v; moo_oop_char_t* vv; moo_oocs_t hs; int index; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_CLASS); if (self) { MOO_ASSERT (moo, MOO_CLASSOF(moo, self) == moo->_class); /* [NOTE] * the loop here assumes that the class has the following * fields in the order shown below: * instvars * classinstvars * classvars */ vv = &self->instvars; for (index = VAR_INSTANCE; index <= VAR_CLASS; index++) { v = vv[index]; hs.ptr = MOO_OBJ_GET_CHAR_SLOT(v); hs.len = MOO_OBJ_GET_SIZE(v); if (find_word_in_string(&hs, name, &pos) >= 0) { super = self->superclass; MOO_ASSERT (moo, super == cc->super_oop); /* 'self' may be MOO_NULL if MOO_NULL has been given for it. * the caller must take good care when interpreting the meaning of * this field */ var->cls = self; goto done; } } super = self->superclass; MOO_ASSERT (moo, super == cc->super_oop); } else { /* the class definition is not available yet. * find the variable in the compiler's own list */ for (index = VAR_INSTANCE; index <= VAR_CLASS; index++) { if (find_word_in_string(&cc->var[index].str, name, &pos) >= 0) { super = cc->super_oop; var->cls = MOO_NULL; /* the current class being compiled */ goto done; } } super = cc->super_oop; } while (super != moo->_nil) { MOO_ASSERT (moo, MOO_CLASSOF(moo, super) == moo->_class); /* [NOTE] * the loop here assumes that the class has the following * fields in the order shown below: * instvars * classvars * classinstvars */ vv = &((moo_oop_class_t)super)->instvars; for (index = VAR_INSTANCE; index <= VAR_CLASS; index++) { v = vv[index]; hs.ptr = MOO_OBJ_GET_CHAR_SLOT(v); hs.len = MOO_OBJ_GET_SIZE(v); if (find_word_in_string(&hs, name, &pos) >= 0) { /* class variables reside in the class where the definition is found. * that means a class variable is found in the definition of * a superclass, the superclass is the placeholder of the * class variable. on the other hand, instance variables and * class instance variables live in the current class being * compiled as they are inherited. */ var->cls = (index == VAR_CLASS)? (moo_oop_class_t)super: self; super = ((moo_oop_class_t)super)->superclass; goto done; } } super = ((moo_oop_class_t)super)->superclass; } moo_seterrnum (moo, MOO_ENOENT); return -1; done: if (super != moo->_nil) { moo_oow_t spec; /* the class being compiled has a superclass */ MOO_ASSERT (moo, MOO_CLASSOF(moo, super) == moo->_class); switch (index) { case VAR_INSTANCE: /* each class has the number of named instance variables * accumulated for inheritance. the position found in the * local variable string can be adjusted by adding the * number in the superclass */ spec = MOO_OOP_TO_SMOOI(((moo_oop_class_t)super)->spec); pos += MOO_CLASS_SPEC_NAMED_INSTVARS(spec); break; case VAR_CLASSINST: spec = MOO_OOP_TO_SMOOI(((moo_oop_class_t)super)->selfspec); pos += MOO_CLASS_SELFSPEC_CLASSINSTVARS(spec); break; case VAR_CLASS: /* [NOTE] * no adjustment is needed. * a class object is composed of three parts. * fixed-part | classinst-variables | class-variabes * the position returned here doesn't consider * class instance variables that can be potentially * placed before the class variables. */ break; } } var->type = index; var->pos = pos; return pos; } static int clone_assignee (moo_t* moo, const moo_oocs_t* name, moo_oow_t* offset) { moo_method_data_t* md = get_cunit_method_data(moo); int n; moo_oow_t old_len; old_len = md->assignees.len; n = copy_string_to(moo, name, &md->assignees, &md->assignees_capa, 1, '\0'); if (n <= -1) return -1; /* update the pointer to of the name. its length is the same. */ /*name->ptr = md->assignees.ptr + old_len;*/ *offset = old_len; return 0; } static int clone_binary_selector (moo_t* moo, const moo_oocs_t* name, moo_oow_t* offset) { moo_method_data_t* md = get_cunit_method_data(moo); int n; moo_oow_t old_len; old_len = md->binsels.len; n = copy_string_to(moo, name, &md->binsels, &md->binsels_capa, 1, '\0'); if (n <= -1) return -1; /* update the pointer to of the name. its length is the same. */ /*name->ptr = md->binsels.ptr + old_len;*/ *offset = old_len; return 0; } static int clone_keyword (moo_t* moo, const moo_oocs_t* name, moo_oow_t* offset) { moo_method_data_t* md = get_cunit_method_data(moo); int n; moo_oow_t old_len; old_len = md->kwsels.len; n = copy_string_to(moo, name, &md->kwsels, &md->kwsels_capa, 1, '\0'); if (n <= -1) return -1; /* update the pointer to of the name. its length is the same. */ /*name->ptr = md->kwsels.ptr + old_len;*/ *offset = old_len; return 0; } static int add_method_name_fragment (moo_t* moo, const moo_oocs_t* name) { /* method name fragments are concatenated without any delimiters */ moo_method_data_t* md = get_cunit_method_data(moo); return copy_string_to(moo, name, &md->name, &md->name_capa, 1, '\0'); } static int method_exists_noseterr (moo_t* moo, const moo_oocs_t* name) { if (moo->c->cunit->cunit_type == MOO_CUNIT_INTERFACE) { /* TODO: remove duplicate code between interface and class */ moo_cunit_interface_t* ifce = (moo_cunit_interface_t*)moo->c->cunit; if (ifce->mth.type == MOO_METHOD_DUAL) { return moo_lookupdic_noseterr(moo, ifce->self_oop->mthdic[MOO_METHOD_INSTANCE], name) != MOO_NULL || moo_lookupdic_noseterr(moo, ifce->self_oop->mthdic[MOO_METHOD_CLASS], name) != MOO_NULL; } else { MOO_ASSERT (moo, ifce->mth.type < MOO_COUNTOF(ifce->self_oop->mthdic)); return moo_lookupdic_noseterr(moo, ifce->self_oop->mthdic[ifce->mth.type], name) != MOO_NULL; } } else { /* this function must be called from the inteface or the class context only */ moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_CLASS); /* check if the current class contains a method of the given name */ if (cc->mth.type == MOO_METHOD_DUAL) { return moo_lookupdic_noseterr(moo, cc->self_oop->mthdic[MOO_METHOD_INSTANCE], name) != MOO_NULL || moo_lookupdic_noseterr(moo, cc->self_oop->mthdic[MOO_METHOD_CLASS], name) != MOO_NULL; } else { MOO_ASSERT (moo, cc->mth.type < MOO_COUNTOF(cc->self_oop->mthdic)); return moo_lookupdic_noseterr(moo, cc->self_oop->mthdic[cc->mth.type], name) != MOO_NULL; } } } static int add_temporary_variable (moo_t* moo, const moo_oocs_t* name) { /* temporary variable names are added to the string with leading * space if it's not the first variable */ moo_method_data_t* md = get_cunit_method_data(moo); return copy_string_to(moo, name, &md->tmprs, &md->tmprs_capa, 1, ' '); } static MOO_INLINE int find_temporary_variable (moo_t* moo, const moo_oocs_t* name, moo_oow_t* xindex) { moo_method_data_t* md = get_cunit_method_data(moo); return find_word_in_string(&md->tmprs, name, xindex); } static moo_oop_nsdic_t add_namespace (moo_t* moo, moo_oop_nsdic_t dic, const moo_oocs_t* name) { /* add a stand-alone namespace that doesn't belong to a class */ moo_oow_t tmp_count = 0; moo_oop_char_t sym; moo_oop_nsdic_t nsdic; moo_oop_association_t ass; moo_pushvolat (moo, (moo_oop_t*)&dic); tmp_count++; sym = (moo_oop_char_t)moo_makesymbol(moo, name->ptr, name->len); if (!sym) goto oops; moo_pushvolat (moo, (moo_oop_t*)&sym); tmp_count++; nsdic = moo_makensdic(moo, moo->_namespace, NAMESPACE_SIZE); if (!nsdic) goto oops; /*moo_pushvolat (moo, &ns); tmp_count++;*/ ass = moo_putatdic(moo, (moo_oop_dic_t)dic, (moo_oop_t)sym, (moo_oop_t)nsdic); if (!ass) goto oops; nsdic = (moo_oop_nsdic_t)ass->value; MOO_STORE_OOP (moo, &nsdic->nsup, (moo_oop_t)dic); MOO_STORE_OOP (moo, (moo_oop_t*)&nsdic->name, (moo_oop_t)sym); moo_popvolats (moo, tmp_count); return nsdic; oops: moo_popvolats (moo, tmp_count); return MOO_NULL; } static moo_oop_nsdic_t attach_nsdic_to_class (moo_t* moo, moo_oop_class_t c) { moo_oow_t tmp_count = 0; moo_oop_nsdic_t nsdic; moo_pushvolat (moo, (moo_oop_t*)&c); tmp_count++; nsdic = moo_makensdic(moo, moo->_namespace, NAMESPACE_SIZE); if (!nsdic) goto oops; MOO_STORE_OOP (moo, &nsdic->nsup, (moo_oop_t)c); /* it points to the owning class as it belongs to a class */ MOO_STORE_OOP (moo, (moo_oop_t*)&nsdic->name, (moo_oop_t)c->name); /* for convenience only */ c->nsdic = nsdic; moo_popvolats (moo, tmp_count); return nsdic; oops: moo_popvolats (moo, tmp_count); return MOO_NULL; } static moo_oop_nsdic_t attach_nsdic_to_interface (moo_t* moo, moo_oop_interface_t c) { moo_oow_t tmp_count = 0; moo_oop_nsdic_t nsdic; moo_pushvolat (moo, (moo_oop_t*)&c); tmp_count++; nsdic = moo_makensdic(moo, moo->_namespace, NAMESPACE_SIZE); if (!nsdic) goto oops; MOO_STORE_OOP (moo, &nsdic->nsup, (moo_oop_t)c); /* it points to the owning interface as it belongs to an interface */ MOO_STORE_OOP (moo, (moo_oop_t*)&nsdic->name, (moo_oop_t)c->name); /* for convenience only */ c->nsdic = nsdic; moo_popvolats (moo, tmp_count); return nsdic; oops: moo_popvolats (moo, tmp_count); return MOO_NULL; } #define PDN_DONT_ADD_NS (1 << 0) #define PDN_ACCEPT_POOLDIC_AS_NS (1 << 1) static int preprocess_dotted_name (moo_t* moo, int flags, moo_oop_nsdic_t topdic, const moo_oocs_t* fqn, const moo_ioloc_t* fqn_loc, moo_oocs_t* name, moo_oop_nsdic_t* ns_oop) { const moo_ooch_t* ptr, * dot; moo_oow_t len; moo_oocs_t seg; moo_oop_nsdic_t dic; moo_oop_association_t ass; int pooldic_gotten = 0; dic = topdic? topdic: moo->sysdic; ptr = fqn->ptr; len = fqn->len; while (1) { seg.ptr = (moo_ooch_t*)ptr; dot = moo_find_oochar (ptr, len, '.'); if (dot) { if (pooldic_gotten) goto wrong_name; seg.len = dot - ptr; if (is_reserved_word(&seg, MOO_NULL)) goto wrong_name; ass = moo_lookupdic_noseterr(moo, (moo_oop_dic_t)dic, &seg); if (ass) { if (MOO_CLASSOF(moo, ass->value) == moo->_namespace) { /* ok - the current segment is a namespace name */ dic = (moo_oop_nsdic_t)ass->value; } else if (MOO_CLASSOF(moo, ass->value) == moo->_class) { /* the segment is a class name. use the nsdic field. * class X {} * class X.Y {} * when processing X in X.Y, this part is reached. */ moo_oop_nsdic_t t; t = ((moo_oop_class_t)ass->value)->nsdic; if ((moo_oop_t)t == moo->_nil) { if (flags & PDN_DONT_ADD_NS) goto wrong_name; /* attach a new namespace dictionary to the nsdic field * of the class */ t = attach_nsdic_to_class(moo, (moo_oop_class_t)ass->value); if (!t) return -1; dic = t; } else dic = t; } else if (MOO_CLASSOF(moo, ass->value) == moo->_interface) { moo_oop_nsdic_t t; t = ((moo_oop_interface_t)ass->value)->nsdic; if ((moo_oop_t)t == moo->_nil) { if (flags & PDN_DONT_ADD_NS) goto wrong_name; /* attach a new namespace dictionary to the nsdic field * of the interface */ t = attach_nsdic_to_interface(moo, (moo_oop_interface_t)ass->value); if (!t) return -1; dic = t; } else dic = t; } else if ((flags & PDN_ACCEPT_POOLDIC_AS_NS) && MOO_CLASSOF(moo, ass->value) == moo->_pool_dictionary) { /* A pool dictionary is treated as if it's a name space. * However, the pool dictionary can only act as a name space * if it's the second last segment. A pool dictionary * cannot be nested in another pool dictionary */ dic = (moo_oop_nsdic_t)ass->value; pooldic_gotten = 1; } else { goto wrong_name; } } else { moo_oop_nsdic_t t; /* the segment does not exist. add it */ if (flags & PDN_DONT_ADD_NS) { /* in '#extend Planet.Earth', it's an error * if Planet doesn't exist */ goto wrong_name; } /* When definining a new class, add a missing namespace */ t = add_namespace(moo, dic, &seg); if (!t) return -1; dic = t; } } else { /* this is the last segment. it should be a class name or an item name */ seg.len = len; if (is_reserved_word(&seg, MOO_NULL)) goto wrong_name; *name = seg; *ns_oop = dic; break; } ptr = dot + 1; len -= seg.len + 1; } return 0; wrong_name: seg.len += seg.ptr - fqn->ptr; seg.ptr = fqn->ptr; moo_setsynerr (moo, MOO_SYNERR_NAMESPACEINVAL, fqn_loc, &seg); return -1; } static int import_pooldic (moo_t* moo, moo_cunit_class_t* cc, moo_oop_nsdic_t ns_oop, const moo_oocs_t* tok_lastseg, const moo_oocs_t* tok_name, const moo_ioloc_t* tok_loc) { moo_oop_association_t ass; moo_oow_t i; /* check if the name refers to a pool dictionary */ ass = moo_lookupdic_noseterr(moo, (moo_oop_dic_t)ns_oop, tok_lastseg); if (!ass || MOO_CLASSOF(moo, ass->value) != moo->_pool_dictionary) { moo_setsynerr (moo, MOO_SYNERR_PDIMPINVAL, tok_loc, tok_name); return -1; } /* check if the same dictionary pool has been declared for import */ for (i = 0; i < cc->pdimp.dics.count; i++) { if (ass->value == cc->pdimp.dics.ptr[i]) { moo_setsynerr (moo, MOO_SYNERR_PDIMPDUPL, tok_loc, tok_name); return -1; } } return add_pooldic_import(moo, cc, tok_name, (moo_oop_dic_t)ass->value); } static int compile_class_level_variables_vbar (moo_t* moo, var_type_t dcl_type) { moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; /* class level variable declaration using vbar. * - no assignment of a default value is allowed * - same syntax as local variable declaration within a method */ do { if (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT) { var_info_t var; /* TODO: more check on variability conflict. if it's a indexed class, check if the superclass is fixed or index. if super is fixed and self is fixed or variable-pointer, no restriction. if super is fixed and self is variable-nonpointer, no instance varaible in the super side and in the self side. if super is variable-pointer, self must be a variable-pointer. can't be fixed either if super is variable-nonpointer, self must be a variable-nonpointer of the same type. can't be fixed either if super is variable-nonpointer, no instance variable is allowed. */ if (dcl_type == VAR_INSTANCE && (cc->flags & CLASS_INDEXED) && (cc->indexed_type != MOO_OBJ_TYPE_OOP)) { /* a non-pointer object cannot have instance variables */ moo_setsynerr (moo, MOO_SYNERR_VARDCLBANNED, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (find_class_level_variable(moo, MOO_NULL, TOKEN_NAME(moo), &var) >= 0 || moo_lookupdic_noseterr(moo, (moo_oop_dic_t)moo->sysdic, TOKEN_NAME(moo)) || /* conflicts with a top global name */ moo_lookupdic_noseterr(moo, (moo_oop_dic_t)cc->ns_oop, TOKEN_NAME(moo))) /* conflicts with a global name in the class'es name space */ { moo_setsynerr (moo, MOO_SYNERR_VARNAMEDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (add_class_level_variable(moo, dcl_type, TOKEN_NAME(moo), TOKEN_LOC(moo)) <= -1) return -1; } else { break; } GET_TOKEN (moo); } while (1); if (!is_token_binary_selector(moo, VOCA_VBAR)) { moo_setsynerr (moo, MOO_SYNERR_VBAR, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); return 0; } static int compile_class_level_variables (moo_t* moo) { moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; var_type_t dcl_type = VAR_INSTANCE; int varacc_type = 0; if (TOKEN_TYPE(moo) == MOO_IOTOK_LPAREN) { /* process variable declaration modifiers */ GET_TOKEN (moo); if (TOKEN_TYPE(moo) != MOO_IOTOK_RPAREN) { do { if (is_token_symbol(moo, VOCA_CLASS_S)) { /* variable(#class) */ if (dcl_type != VAR_INSTANCE) { moo_setsynerr (moo, MOO_SYNERR_MODIFIERDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } dcl_type = VAR_CLASS; GET_TOKEN (moo); } else if (is_token_symbol(moo, VOCA_CLASSINST_S)) { /* variable(#classinst) */ if (dcl_type != VAR_INSTANCE) { moo_setsynerr (moo, MOO_SYNERR_MODIFIERDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } dcl_type = VAR_CLASSINST; GET_TOKEN (moo); } else if (is_token_symbol(moo, VOCA_GET_S)) { /* variable(#get) */ if (varacc_type & VARACC_GETTER) { moo_setsynerr (moo, MOO_SYNERR_MODIFIERDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } varacc_type |= VARACC_GETTER; GET_TOKEN (moo); } else if (is_token_symbol(moo, VOCA_SET_S)) { /* variable(#set) */ if (varacc_type & VARACC_SETTER) { moo_setsynerr (moo, MOO_SYNERR_MODIFIERDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } varacc_type |= VARACC_SETTER; GET_TOKEN (moo); } else if (TOKEN_TYPE(moo) == MOO_IOTOK_COMMA || TOKEN_TYPE(moo) == MOO_IOTOK_EOF || TOKEN_TYPE(moo) == MOO_IOTOK_RPAREN) { /* no modifier is present */ moo_setsynerr (moo, MOO_SYNERR_MODIFIER, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } else { /* invalid modifier */ moo_setsynerr (moo, MOO_SYNERR_MODIFIERINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (TOKEN_TYPE(moo) != MOO_IOTOK_COMMA) break; /* hopefully ) */ GET_TOKEN (moo); /* get the token after , */ } while (1); } if (TOKEN_TYPE(moo) != MOO_IOTOK_RPAREN) { /* ) expected */ moo_setsynerr (moo, MOO_SYNERR_RPAREN, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); } /* variable declaration */ do { if (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT) { var_info_t var; /* TODO: more check on variability conflict. if it's a indexed class, check if the superclass is fixed or index. if super is fixed and self is fixed or variable-pointer, no restriction. if super is fixed and self is variable-nonpointer, no instance varaible in the super side and in the self side. if super is variable-pointer, self must be a variable-pointer. can't be fixed either if super is variable-nonpointer, self must be a variable-nonpointer of the same type. can't be fixed either if super is variable-nonpointer, no instance variable is allowed. */ if (dcl_type == VAR_INSTANCE && (cc->flags & CLASS_INDEXED) && (cc->indexed_type != MOO_OBJ_TYPE_OOP)) { /* a non-pointer object cannot have instance variables */ moo_setsynerr (moo, MOO_SYNERR_VARDCLBANNED, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (find_class_level_variable(moo, MOO_NULL, TOKEN_NAME(moo), &var) >= 0 || moo_lookupdic_noseterr(moo, (moo_oop_dic_t)moo->sysdic, TOKEN_NAME(moo)) || /* conflicts with a top global name */ moo_lookupdic_noseterr(moo, (moo_oop_dic_t)cc->ns_oop, TOKEN_NAME(moo))) /* conflicts with a global name in the class'es name space */ { moo_setsynerr (moo, MOO_SYNERR_VARNAMEDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (add_class_level_variable(moo, dcl_type, TOKEN_NAME(moo), TOKEN_LOC(moo)) <= -1) return -1; GET_TOKEN (moo); if (TOKEN_TYPE(moo) == MOO_IOTOK_ASSIGN) { moo_oop_t lit; GET_TOKEN (moo); /* [NOTE] default value assignment. only a literal is allowed. * the initial values for instance variables and * class instance variables are set to read-only. * I may change this if i get the actual initial * value assignment upon instantiation to employ * deep-copying in moo_instantiate() and in the compiler. */ lit = token_to_literal(moo, dcl_type != VAR_CLASS); if (!lit) return -1; /* set the initial value for the variable added above */ if (set_class_level_variable_initv(moo, dcl_type, cc->var[dcl_type].count - 1, lit, varacc_type) <= -1) return -1; GET_TOKEN (moo); } else if (varacc_type) { /* this part is to remember the variable access type that indicates * whether to generate a getter method and a setter method */ if (set_class_level_variable_initv(moo, dcl_type, cc->var[dcl_type].count - 1, MOO_NULL, varacc_type) <= -1) return -1; } } else if (TOKEN_TYPE(moo) == MOO_IOTOK_COMMA || TOKEN_TYPE(moo) == MOO_IOTOK_EOF || TOKEN_TYPE(moo) == MOO_IOTOK_PERIOD) { /* no variable name is present */ moo_setsynerrbfmt (moo, MOO_SYNERR_IDENT, TOKEN_LOC(moo), TOKEN_NAME(moo), "variable name expected"); return -1; } else { break; } if (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT) { moo_setsynerr (moo, MOO_SYNERR_COMMA, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } else if (TOKEN_TYPE(moo) != MOO_IOTOK_COMMA) break; /* hopefully . */ GET_TOKEN (moo); } while (1); if (TOKEN_TYPE(moo) != MOO_IOTOK_PERIOD) { moo_setsynerr (moo, MOO_SYNERR_PERIOD, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); return 0; } static int compile_class_level_imports (moo_t* moo) { /* pool dictionary import declaration * import ... */ moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; moo_oocs_t last; moo_oop_nsdic_t ns_oop; if (TOKEN_TYPE(moo) == MOO_IOTOK_LPAREN) { /* process import declaration modifiers */ GET_TOKEN (moo); if (TOKEN_TYPE(moo) != MOO_IOTOK_RPAREN) { do { #if 0 /* TODO: do i need any modifier for import declaration? * import(#pooldic) - import a pool dictionary specifically? * import(#xxxxx) - to do what??? */ if (is_token_symbol(moo, VOCA_POOLDIC_S)) { /* import(#pooldic) - import a pool dictionary */ if (dcl_type != IMPORT_POOLDIC) { moo_setsynerr (moo, MOO_SYNERR_MODIFIERDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } dcl_type = IMPORT_POOLDIC; GET_TOKEN (moo); } else #endif if (TOKEN_TYPE(moo) == MOO_IOTOK_COMMA || TOKEN_TYPE(moo) == MOO_IOTOK_EOF || TOKEN_TYPE(moo) == MOO_IOTOK_RPAREN) { /* no modifier is present */ moo_setsynerr (moo, MOO_SYNERR_MODIFIER, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } else { /* invalid modifier */ moo_setsynerr (moo, MOO_SYNERR_MODIFIERINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (TOKEN_TYPE(moo) != MOO_IOTOK_COMMA) break; /* hopefully ) */ GET_TOKEN (moo); /* get the token after , */ } while (1); } if (TOKEN_TYPE(moo) != MOO_IOTOK_RPAREN) { /* ) expected */ moo_setsynerr (moo, MOO_SYNERR_RPAREN, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); } do { if (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT_DOTTED) { if (preprocess_dotted_name(moo, 0, MOO_NULL, TOKEN_NAME(moo), TOKEN_LOC(moo), &last, &ns_oop) <= -1) return -1; } else if (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT) { last = *TOKEN_NAME(moo); /* it falls back to the name space of the class */ ns_oop = cc->ns_oop; } else if (TOKEN_TYPE(moo) == MOO_IOTOK_COMMA || TOKEN_TYPE(moo) == MOO_IOTOK_EOF || TOKEN_TYPE(moo) == MOO_IOTOK_PERIOD) { /* no variable name is present */ moo_setsynerrbfmt (moo, MOO_SYNERR_IDENT, TOKEN_LOC(moo), TOKEN_NAME(moo), "pool dictionary name expected"); return -1; } else { break; } if (import_pooldic(moo, cc, ns_oop, &last, TOKEN_NAME(moo), TOKEN_LOC(moo)) <= -1) return -1; GET_TOKEN (moo); if (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT_DOTTED || TOKEN_TYPE(moo) == MOO_IOTOK_IDENT) { moo_setsynerr (moo, MOO_SYNERR_COMMA, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } else if (TOKEN_TYPE(moo) != MOO_IOTOK_COMMA) break; /* hopefully . */ GET_TOKEN (moo); } while (1); if (TOKEN_TYPE(moo) != MOO_IOTOK_PERIOD) { moo_setsynerr (moo, MOO_SYNERR_PERIOD, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); return 0; } static int compile_unary_method_name (moo_t* moo) { moo_method_data_t* md = get_cunit_method_data(moo); MOO_ASSERT (moo, md->name.len == 0); MOO_ASSERT (moo, md->tmpr_nargs == 0); if (add_method_name_fragment(moo, TOKEN_NAME(moo)) <= -1) return -1; GET_TOKEN (moo); if (TOKEN_TYPE(moo) == MOO_IOTOK_LPAREN) { /* this is a procedural style method */ MOO_ASSERT (moo, md->tmpr_nargs == 0); GET_TOKEN (moo); if (TOKEN_TYPE(moo) != MOO_IOTOK_RPAREN) { do { if (TOKEN_TYPE(moo) != MOO_IOTOK_IDENT) { /* wrong argument name. identifier is expected */ moo_setsynerr (moo, MOO_SYNERR_IDENT, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (find_temporary_variable(moo, TOKEN_NAME(moo), MOO_NULL) >= 0) { moo_setsynerr (moo, MOO_SYNERR_ARGNAMEDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (add_temporary_variable(moo, TOKEN_NAME(moo)) <= -1) return -1; md->tmpr_nargs++; GET_TOKEN (moo); if (TOKEN_TYPE(moo) == MOO_IOTOK_RPAREN) break; if (TOKEN_TYPE(moo) != MOO_IOTOK_COMMA) { moo_setsynerr (moo, MOO_SYNERR_COMMA, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); } while (1); } GET_TOKEN (moo); return 9999; /* to indicate the method definition is in a procedural style */ } return 0; } static int compile_binary_method_name (moo_t* moo) { moo_method_data_t* md = get_cunit_method_data(moo); MOO_ASSERT (moo, md->name.len == 0); MOO_ASSERT (moo, md->tmpr_nargs == 0); if (add_method_name_fragment(moo, TOKEN_NAME(moo)) <= -1) return -1; GET_TOKEN (moo); /* collect the argument name */ if (TOKEN_TYPE(moo) != MOO_IOTOK_IDENT) { /* wrong argument name. identifier expected */ moo_setsynerr (moo, MOO_SYNERR_IDENT, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } MOO_ASSERT (moo, md->tmpr_nargs == 0); /* no duplication check is performed against class-level variable names. * a duplcate name will shade a previsouly defined variable. */ if (add_temporary_variable(moo, TOKEN_NAME(moo)) <= -1) return -1; md->tmpr_nargs++; MOO_ASSERT (moo, md->tmpr_nargs == 1); /* this check should not be not necessary if (md->tmpr_nargs > MAX_CODE_NARGS) { moo_setsynerr (moo, MOO_SYNERR_ARGFLOOD, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } */ GET_TOKEN (moo); return 0; } static int compile_keyword_method_name (moo_t* moo) { moo_method_data_t* md = get_cunit_method_data(moo); MOO_ASSERT (moo, md->name.len == 0); MOO_ASSERT (moo, md->tmpr_nargs == 0); do { if (add_method_name_fragment(moo, TOKEN_NAME(moo)) <= -1) return -1; GET_TOKEN (moo); if (TOKEN_TYPE(moo) != MOO_IOTOK_IDENT) { /* wrong argument name. identifier is expected */ moo_setsynerr (moo, MOO_SYNERR_IDENT, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (find_temporary_variable(moo, TOKEN_NAME(moo), MOO_NULL) >= 0) { moo_setsynerr (moo, MOO_SYNERR_ARGNAMEDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (add_temporary_variable(moo, TOKEN_NAME(moo)) <= -1) return -1; md->tmpr_nargs++; GET_TOKEN (moo); } while (TOKEN_TYPE(moo) == MOO_IOTOK_KEYWORD); return 0; } static int compile_method_name (moo_t* moo) { /* * method-name := unary-method-name | binary-method-name | keyword-method-name * unary-method-name := unary-selector * binary-method-name := binary-selector selector-argument * keyword-method-name := (keyword selector-argument)+ * selector-argument := identifier * unary-selector := identifier */ moo_method_data_t* md = get_cunit_method_data(moo); int n; MOO_ASSERT (moo, md->tmpr_count == 0); md->name_loc = *TOKEN_LOC(moo); switch (TOKEN_TYPE(moo)) { case MOO_IOTOK_IDENT: n = compile_unary_method_name(moo); break; case MOO_IOTOK_BINSEL: n = compile_binary_method_name(moo); break; case MOO_IOTOK_KEYWORD: n = compile_keyword_method_name(moo); break; default: /* illegal method name */ moo_setsynerr (moo, MOO_SYNERR_MTHNAME, TOKEN_LOC(moo), TOKEN_NAME(moo)); n = -1; break; } if (n <= -1) return -1; if (method_exists_noseterr(moo, &md->name)) { moo_setsynerr (moo, MOO_SYNERR_MTHNAMEDUPL, &md->name_loc, &md->name); return -1; } /* compile_unary_method_name() returns 9999 if the name is followed by () */ if (md->variadic && n != 9999) { moo_setsynerr (moo, MOO_SYNERR_VARIADMTHINVAL, &md->name_loc, &md->name); return -1; } MOO_ASSERT (moo, md->tmpr_nargs < MAX_CODE_NARGS); /* the total number of temporaries is equal to the number of * arguments after having processed the message pattern. it's because * moo treats arguments the same as temporaries */ md->tmpr_count = md->tmpr_nargs; return 0; } static int compile_method_temporaries (moo_t* moo) { /* * method-temporaries := "|" variable-list "|" * variable-list := identifier* */ moo_method_data_t* md = get_cunit_method_data(moo); if (!is_token_binary_selector(moo, VOCA_VBAR)) { /* return without doing anything if | is not found. * this is not an error condition */ return 0; } GET_TOKEN (moo); while (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT) { /* a temporary variable name may get the same as a class level variable name * or even a class name in such as case, it shadows the class level variable * name or the class name. however, it can't be the same as another temporary * variable */ if (find_temporary_variable(moo, TOKEN_NAME(moo), MOO_NULL) >= 0) { moo_setsynerr (moo, MOO_SYNERR_TMPRNAMEDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (add_temporary_variable(moo, TOKEN_NAME(moo)) <= -1) return -1; md->tmpr_count++; if (md->tmpr_count > MAX_CODE_NARGS) { moo_setsynerr (moo, MOO_SYNERR_TMPRFLOOD, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); } if (!is_token_binary_selector(moo, VOCA_VBAR)) { moo_setsynerr (moo, MOO_SYNERR_VBAR, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); return 0; } static int compile_method_pragma (moo_t* moo) { /* * method-pragma := "<" "primitive:" integer ">" | * "<" "primitive:" symbol ">" | * "<" "exception" ">" | * "<" "ensure" ">" */ moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; moo_ooi_t pfnum; const moo_ooch_t* ptr, * end; if (!is_token_binary_selector(moo, VOCA_LT)) { /* return if < is not seen. it is not an error condition */ return 0; } GET_TOKEN (moo); if (is_token_keyword(moo, VOCA_PRIMITIVE_COLON)) { GET_TOKEN (moo); switch (TOKEN_TYPE(moo)) { case MOO_IOTOK_INTLIT: /*TODO: more checks the validity of the primitive number. support number with radix and so on support more extensive syntax. support primitive name, not number*/ ptr = TOKEN_NAME_PTR(moo); end = ptr + TOKEN_NAME_LEN(moo); pfnum = 0; while (ptr < end && is_digitchar(*ptr)) { pfnum = pfnum * 10 + (*ptr - '0'); if (!MOO_OOI_IN_METHOD_PREAMBLE_INDEX_RANGE(pfnum)) { moo_setsynerr (moo, MOO_SYNERR_PFNUMINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } ptr++; } /* TODO: check if the number points to one of the internal primitive function */ cc->mth.pfnum = pfnum; break; case MOO_IOTOK_SYMLIT: { const moo_ooch_t* tptr; moo_oow_t tlen; moo_pfbase_t* pfbase; tptr = TOKEN_NAME_PTR(moo) + 1; tlen = TOKEN_NAME_LEN(moo) - 1; /* attempt get a primitive function number by name */ pfbase = moo_getpfnum(moo, tptr, tlen, &pfnum); if (!pfbase) { /* a built-in primitive function is not found * check if it is a primitive function identifier */ moo_oow_t lit_idx; if (!moo_rfind_oochar(tptr, tlen, '.')) { /* wrong primitive function identifier */ moo_setsynerr (moo, MOO_SYNERR_PFIDINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } /* external named primitive containing a period. */ /* perform some sanity checks. see compile_method_definition() for similar checks */ pfbase = moo_querymodpf(moo, tptr, tlen, MOO_NULL); if (!pfbase) { MOO_DEBUG2 (moo, "Cannot find module primitive function - %.*js\n", tlen, tptr); moo_setsynerr (moo, MOO_SYNERR_PFIDINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (cc->mth.tmpr_nargs < pfbase->minargs || cc->mth.tmpr_nargs > pfbase->maxargs) { MOO_DEBUG5 (moo, "Unsupported argument count in primitive method definition of %.*js - %zd-%zd expected, %zd specified\n", tlen, tptr, pfbase->minargs, pfbase->maxargs, cc->mth.tmpr_nargs); moo_setsynerr (moo, MOO_SYNERR_PFARGDEFINVAL, &cc->mth.name_loc, &cc->mth.name); return -1; } if (add_symbol_literal(moo, TOKEN_NAME(moo), 1, &lit_idx) <= -1) return -1; /* the primitive definition is placed at the very beginning of a method defintion. * so the index to the symbol registered should be very small like 0 */ MOO_ASSERT (moo, MOO_OOI_IN_METHOD_PREAMBLE_INDEX_RANGE(lit_idx)); cc->mth.pftype = PFTYPE_NAMED; cc->mth.pfnum = lit_idx; } else if (!MOO_OOI_IN_METHOD_PREAMBLE_INDEX_RANGE(pfnum)) { moo_setsynerr (moo, MOO_SYNERR_PFIDINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } else { if (cc->mth.tmpr_nargs < pfbase->minargs || cc->mth.tmpr_nargs > pfbase->maxargs) { MOO_DEBUG5 (moo, "Unsupported argument count in primitive method definition of %.*js - %zd-%zd expected, %zd specified\n", tlen, tptr, pfbase->minargs, pfbase->maxargs, cc->mth.tmpr_nargs); moo_setsynerr (moo, MOO_SYNERR_PFARGDEFINVAL, &cc->mth.name_loc, &cc->mth.name); return -1; } cc->mth.pftype = PFTYPE_NUMBERED; cc->mth.pfnum = pfnum; } break; } default: moo_setsynerr (moo, MOO_SYNERR_INTEGER, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } } else if (is_token_word(moo, VOCA_EXCEPTION)) { /* TODO: exception handler is supposed to be used by BlockContext on:do:. * it needs to check the number of arguments at least */ cc->mth.pftype = PFTYPE_EXCEPTION; } else if (is_token_word(moo, VOCA_ENSURE)) { cc->mth.pftype = PFTYPE_ENSURE; } else { moo_setsynerr (moo, MOO_SYNERR_PRIMITIVE, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); if (!is_token_binary_selector(moo, VOCA_GT)) { moo_setsynerr (moo, MOO_SYNERR_GT, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); return 0; } static int validate_class_level_variable (moo_t* moo, var_info_t* var, const moo_oocs_t* name, const moo_ioloc_t* name_loc) { moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; switch (var->type) { case VAR_INSTANCE: if (cc->mth.type == MOO_METHOD_CLASS || cc->mth.type == MOO_METHOD_DUAL) { /* a class(or dual) method cannot access an instance variable */ moo_setsynerr (moo, MOO_SYNERR_VARINACC, name_loc, name); return -1; } break; case VAR_CLASSINST: /* class instance variable can be accessed by only pure class methods */ if (cc->mth.type == MOO_METHOD_INSTANCE || cc->mth.type == MOO_METHOD_DUAL) { /* an instance method cannot access a class-instance variable */ moo_setsynerr (moo, MOO_SYNERR_VARINACC, name_loc, name); return -1; } /* to a class object itself, a class-instance variable is * just an instance variriable. but these are located * after the named instance variables. */ var->pos += MOO_CLASS_NAMED_INSTVARS; break; case VAR_CLASS: /* a class variable can be accessed by both instance methods and class methods */ MOO_ASSERT (moo, var->cls != MOO_NULL); MOO_ASSERT (moo, MOO_CLASSOF(moo, var->cls) == moo->_class); /* increment the position by the number of class instance variables * as the class variables are placed after the class instance variables */ var->pos += MOO_CLASS_NAMED_INSTVARS + MOO_CLASS_SELFSPEC_CLASSINSTVARS(MOO_OOP_TO_SMOOI(var->cls->selfspec)); break; default: /* internal error - it must not happen */ moo_seterrnum (moo, MOO_EINTERN); return -1; } return 0; } static moo_oow_t is_dotted_ident_prefixed (const moo_oocs_t* name, int voca_id) { if (name->len > vocas[voca_id].len && moo_equal_oochars(name->ptr, vocas[voca_id].str, vocas[voca_id].len) && name->ptr[vocas[voca_id].len] == '.') return vocas[voca_id].len; return 0; } static MOO_INLINE int find_dotted_ident (moo_t* moo, const moo_oocs_t* name, const moo_ioloc_t* name_loc, var_info_t* var) { /* if a name is dotted, * * self.XXX - instance variable * A.B.C - namespace or pool dictionary related reference. * self.B.C - B.C under the current class where B is not an instance variable */ moo_oocs_t last; moo_oop_nsdic_t top_dic, ns_oop; moo_oop_association_t ass; moo_oow_t pxlen; moo_oocs_t xname; moo_ioloc_t xname_loc; top_dic = MOO_NULL; xname = *name; xname_loc = *name_loc; if ((pxlen = is_dotted_ident_prefixed(name, VOCA_SELF)) > 0) { /* the first word in the dotted notation is self */ if (!moo_find_oochar(name->ptr + pxlen + 1, name->len - pxlen - 1, '.')) { /* the dotted name is composed of 2 segments only */ last.ptr = name->ptr + pxlen + 1; last.len = name->len - pxlen - 1; if (is_reserved_word(&last, MOO_NULL)) { /* self. is followed by a reserved word. * a proper variable name is expected. */ moo_setsynerr (moo, MOO_SYNERR_VARNAME, name_loc, name); return -1; } MOO_ASSERT (moo, moo->c->cunit != MOO_NULL); switch (moo->c->cunit->cunit_type) { case MOO_CUNIT_POOLDIC: { moo_oop_t v; /* called inside a pooldic definition */ MOO_ASSERT (moo, ((moo_cunit_pooldic_t*)moo->c->cunit)->ns_oop != MOO_NULL); MOO_ASSERT (moo, ((moo_cunit_pooldic_t*)moo->c->cunit)->pd_oop == MOO_NULL); v = find_element_in_compiling_pooldic(moo, &last); if (!v) { moo_setsynerr (moo, MOO_SYNERR_VARUNDCL, name_loc, name); return -1; } var->type = VAR_LITERAL; var->u.lit = v; /* TODO: change this */ return 0; } case MOO_CUNIT_CLASS: { moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; /* called inside a class definition */ MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_CLASS); if (cc->in_class_body) { /* [NOTE] * cls.ns_oop is set when the class name is enountered. * cls.super_oop is set when the parent class name is enountered. * cls.super_oop may still be MOO_NULL even if cls.ns_oop is not. * on the other hand, cls.ns_oop is not MOO_NULL as long as * cls.super_oop is not MOO_NULL. */ MOO_ASSERT (moo, cc->super_oop != MOO_NULL); MOO_ASSERT (moo, cc->ns_oop != MOO_NULL); /* cc->self_oop may still be MOO_NULL if the class has not been instantiated */ if (find_class_level_variable(moo, cc->self_oop, &last, var) >= 0) { /* if the current class has not been instantiated, * no validation nor adjustment of the var->pos field is performed */ if (cc->self_oop && validate_class_level_variable(moo, var, name, name_loc) <= -1) return -1; return 0; } } if (!cc->self_oop) goto varinacc; top_dic = cc->self_oop->nsdic; /* namespace that the current interface starts */ pxlen++; /* include . into the length */ break; } case MOO_CUNIT_INTERFACE: { moo_cunit_interface_t* ifce = (moo_cunit_interface_t*)moo->c->cunit; if (!ifce->self_oop) goto varinacc; top_dic = ifce->self_oop->nsdic; /* namespace that the current interface starts */ pxlen++; /* include . into the length */ break; } default: varinacc: moo_setsynerr (moo, MOO_SYNERR_VARINACC, name_loc, name); return -1; } } else { /* more than 3 segments. e.g. self.XXX.YYY */ switch (moo->c->cunit->cunit_type) { case MOO_CUNIT_POOLDIC: /* a pooldic definition cannot contain subdictionaries. * the pooldic is a terminal point and the items inside * are final nodes. if self is followed by more than 1 * subsegments, it's an error. */ goto varinacc; case MOO_CUNIT_CLASS: { moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; if (!cc->self_oop) goto varinacc; top_dic = cc->self_oop->nsdic; /* namespace that the current class starts */ pxlen++; /* include . into the length */ break; } case MOO_CUNIT_INTERFACE: { moo_cunit_interface_t* ifce = (moo_cunit_interface_t*)moo->c->cunit; if (!ifce->self_oop) goto varinacc; top_dic = ifce->self_oop->nsdic; /* namespace that the current interface starts */ pxlen++; /* include . into the length */ break; } default: goto varinacc; } } } else if ((pxlen = is_dotted_ident_prefixed(name, VOCA_SELFNS)) > 0) { /* the first segment is selfns which indicates a namespace that * the current class belongs to */ switch (moo->c->cunit->cunit_type) { case MOO_CUNIT_POOLDIC: /* compiling in a pooldic definition */ top_dic = ((moo_cunit_pooldic_t*)moo->c->cunit)->ns_oop; MOO_ASSERT (moo, top_dic != MOO_NULL); break; case MOO_CUNIT_CLASS: /* compiling in a class definition */ top_dic = ((moo_cunit_class_t*)moo->c->cunit)->ns_oop; MOO_ASSERT (moo, top_dic != MOO_NULL); break; case MOO_CUNIT_INTERFACE: /* compiling in an interface definition */ top_dic = ((moo_cunit_interface_t*)moo->c->cunit)->ns_oop; MOO_ASSERT (moo, top_dic != MOO_NULL); break; default: moo_setsynerr (moo, MOO_SYNERR_VARINACC, name_loc, name); return -1; } pxlen++; /* include . into the length */ } if ((moo_oop_t)top_dic == moo->_nil) top_dic = MOO_NULL; xname.ptr += pxlen; xname.len -= pxlen; xname_loc.colm += pxlen; if (preprocess_dotted_name(moo, PDN_DONT_ADD_NS | PDN_ACCEPT_POOLDIC_AS_NS, top_dic, &xname, &xname_loc, &last, &ns_oop) <= -1) return -1; ass = moo_lookupdic_noseterr(moo, (moo_oop_dic_t)ns_oop, &last); if (!ass) { /* undeclared identifier */ moo_setsynerr (moo, MOO_SYNERR_VARUNDCL, name_loc, name); return -1; } var->type = VAR_GLOBAL; var->u.gbl = ass; return 0; } static MOO_INLINE int find_undotted_ident (moo_t* moo, const moo_oocs_t* name, const moo_ioloc_t* name_loc, var_info_t* var) { moo_oow_t index; moo_oop_association_t ass; switch (moo->c->cunit->cunit_type) { case MOO_CUNIT_POOLDIC: { moo_oop_t v; /* called inside a pooldic definition */ MOO_ASSERT (moo, ((moo_cunit_pooldic_t*)moo->c->cunit)->ns_oop != MOO_NULL); MOO_ASSERT (moo, ((moo_cunit_pooldic_t*)moo->c->cunit)->pd_oop == MOO_NULL); v = find_element_in_compiling_pooldic(moo, name); if (!v) { moo_setsynerr (moo, MOO_SYNERR_VARUNDCL, name_loc, name); return -1; } var->type = VAR_LITERAL; var->u.lit = v; /* TODO: change this */ break; } case MOO_CUNIT_CLASS: { moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; if (cc->self_oop) { /* the current class being compiled has been instantiated. * look up in the temporary variable list if compiling in a method */ if (cc->mth.active && find_temporary_variable(moo, name, &index) >= 0) { var->type = (index < cc->mth.tmpr_nargs)? VAR_ARGUMENT: VAR_TEMPORARY; var->pos = index; return 0; } } if (cc->in_class_body) { /* called inside a class definition */ /* read a comment in find_dotted_ident() for the reason behind * the 'if' condition above. */ MOO_ASSERT (moo, cc->super_oop != MOO_NULL); MOO_ASSERT (moo, cc->ns_oop != MOO_NULL); if (find_class_level_variable(moo, cc->self_oop, name, var) >= 0) { /* if the current class being compiled has not been instantiated, * no validation nor adjustment of the var->pos field is performed */ return cc->self_oop? validate_class_level_variable(moo, var, name, name_loc): 0; } } /* find an undotted identifier in dictionaries */ if (cc->ns_oop) { ass = moo_lookupdic_noseterr(moo, (moo_oop_dic_t)cc->ns_oop, name); /* in the current name space */ if (!ass && cc->ns_oop != moo->sysdic) ass = moo_lookupdic_noseterr(moo, (moo_oop_dic_t)moo->sysdic, name); /* in the top-level system dictionary */ } else { ass = moo_lookupdic_noseterr(moo, (moo_oop_dic_t)moo->sysdic, name); /* in the top-level system dictionary */ } if (!ass) { moo_oow_t i; moo_oop_association_t ass2 = MOO_NULL; /* attempt to find the variable in pool dictionaries */ for (i = 0; i < cc->pdimp.dics.count; i++) { ass = moo_lookupdic_noseterr(moo, (moo_dic_t*)cc->pdimp.dics.ptr[i], name); if (ass) { if (ass2) { /* the variable name has been found at least in 2 dictionaries - ambiguous */ moo_setsynerr (moo, MOO_SYNERR_VARAMBIG, name_loc, name); return -1; } ass2 = ass; } } ass = ass2; if (!ass) { moo_setsynerr (moo, MOO_SYNERR_VARUNDCL, name_loc, name); return -1; } } var->type = VAR_GLOBAL; var->u.gbl = ass; break; } case MOO_CUNIT_INTERFACE: { moo_cunit_interface_t* ifce = ((moo_cunit_interface_t*)moo->c->cunit); if (ifce->self_oop) { /* the current interface being compiled has been instantiated. * look up in the temporary variable list if compiling in a method */ if (ifce->mth.active && find_temporary_variable(moo, name, &index) >= 0) { var->type = (index < ifce->mth.tmpr_nargs)? VAR_ARGUMENT: VAR_TEMPORARY; var->pos = index; return 0; } } if (ifce->ns_oop) { ass = moo_lookupdic_noseterr(moo, (moo_oop_dic_t)ifce->ns_oop, name); /* in the current name space */ if (!ass && ifce->ns_oop != moo->sysdic) ass = moo_lookupdic_noseterr(moo, (moo_oop_dic_t)moo->sysdic, name); /* in the top-level system dictionary */ } else { ass = moo_lookupdic_noseterr(moo, (moo_oop_dic_t)moo->sysdic, name); /* in the top-level system dictionary */ } if (!ass) { moo_setsynerr (moo, MOO_SYNERR_VARUNDCL, name_loc, name); return -1; } var->type = VAR_GLOBAL; var->u.gbl = ass; break; } default: /* it must not happen. internal error if it happens */ moo_setsynerr (moo, MOO_SYNERR_VARINACC, name_loc, name); return -1; } return 0; } static int get_variable_info (moo_t* moo, const moo_oocs_t* name, const moo_ioloc_t* name_loc, int name_dotted, var_info_t* var) { int x; MOO_MEMSET (var, 0, MOO_SIZEOF(*var)); x = name_dotted? find_dotted_ident(moo, name, name_loc, var): find_undotted_ident(moo, name, name_loc, var); if (x <= -1) return -1; /* final validation on the range of pos field. just check regardless of var->type. * i assume the functions above don't taint the var-type field when it's meaningless. */ if (var->pos > MAX_CODE_INDEX) { /* the assignee is not usable because its index is too large * to be expressed in byte-codes. */ moo_setsynerr (moo, MOO_SYNERR_VARUNUSE, name_loc, name); return -1; } return 0; } static int compile_block_temporaries (moo_t* moo) { /* * block-temporaries := "|" variable-list "|" * variable-list := identifier* */ moo_method_data_t* md = get_cunit_method_data(moo); if (!is_token_binary_selector(moo, VOCA_VBAR)) { /* return without doing anything if | is not found. * this is not an error condition */ return 0; } GET_TOKEN (moo); while (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT) { if (find_temporary_variable(moo, TOKEN_NAME(moo), MOO_NULL) >= 0) { moo_setsynerr (moo, MOO_SYNERR_TMPRNAMEDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (add_temporary_variable(moo, TOKEN_NAME(moo)) <= -1) return -1; md->tmpr_count++; if (md->tmpr_count > MAX_CODE_NTMPRS) { moo_setsynerr (moo, MOO_SYNERR_TMPRFLOOD, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); } if (!is_token_binary_selector(moo, VOCA_VBAR)) { moo_setsynerr (moo, MOO_SYNERR_VBAR, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); return 0; } static int store_tmpr_count_for_block (moo_t* moo, moo_oow_t tmpr_count) { moo_method_data_t* md = get_cunit_method_data(moo); if (md->blk_depth >= md->blk_tmprcnt_capa) { moo_oow_t* tmp; moo_oow_t new_capa; new_capa = MOO_ALIGN(md->blk_depth + 1, BLK_TMPRCNT_BUFFER_ALIGN); tmp = (moo_oow_t*)moo_reallocmem(moo, md->blk_tmprcnt, new_capa * MOO_SIZEOF(*tmp)); if (!tmp) return -1; md->blk_tmprcnt_capa = new_capa; md->blk_tmprcnt = tmp; } /* [NOTE] i don't increment blk_depth here. it's updated * by the caller after this function has been called for * a new block entered. */ md->blk_tmprcnt[md->blk_depth] = tmpr_count; return 0; } static int compile_block_expression (moo_t* moo) { moo_method_data_t* md = get_cunit_method_data(moo); moo_oow_t jump_inst_pos; moo_oow_t saved_tmpr_count, saved_tmprs_len; moo_oow_t block_arg_count, block_tmpr_count, code_start; moo_ioloc_t block_loc, colon_loc, tmpr_loc; /* * block-expression := "[" block-body "]" * block-body := (block-argument* "|")? block-temporaries? method-statement* * block-argument := ":" identifier */ /* this function expects [ not to be consumed away */ MOO_ASSERT (moo, TOKEN_TYPE(moo) == MOO_IOTOK_LBRACK); if (md->loop) { /* this [] block is placed inside the {} loop. * this counter is used to check if 'break' or 'countinue' is * placed inside [], which is prohibited. */ md->loop->blkcount++; } block_loc = *TOKEN_LOC(moo); GET_TOKEN (moo); saved_tmprs_len = md->tmprs.len; saved_tmpr_count = md->tmpr_count; MOO_ASSERT (moo, md->blk_depth > 0); MOO_ASSERT (moo, md->blk_tmprcnt[md->blk_depth - 1] == saved_tmpr_count); if (TOKEN_TYPE(moo) == MOO_IOTOK_COLON) { colon_loc = moo->c->tok.loc; /* block temporary variables - argument to blocks */ do { GET_TOKEN (moo); if (TOKEN_TYPE(moo) != MOO_IOTOK_IDENT) { /* wrong argument name. identifier expected */ moo_setsynerr (moo, MOO_SYNERR_IDENT, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } /* TODO: check conflicting names as well */ if (find_temporary_variable(moo, TOKEN_NAME(moo), MOO_NULL) >= 0) { moo_setsynerr (moo, MOO_SYNERR_BLKARGNAMEDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (add_temporary_variable(moo, TOKEN_NAME(moo)) <= -1) return -1; md->tmpr_count++; if (md->tmpr_count > MAX_CODE_NARGS) { moo_setsynerr (moo, MOO_SYNERR_BLKARGFLOOD, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); } while (TOKEN_TYPE(moo) == MOO_IOTOK_COLON); if (!is_token_binary_selector(moo, VOCA_VBAR)) { moo_setsynerr (moo, MOO_SYNERR_VBAR, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); } block_arg_count = md->tmpr_count - saved_tmpr_count; if (block_arg_count > MAX_CODE_NBLKARGS) { /* while an integer object is pused to indicate the number of * block arguments, evaluation which is done by message passing * limits the number of arguments that can be passed. so the * check is implemented */ moo_setsynerr (moo, MOO_SYNERR_BLKARGFLOOD, &colon_loc, MOO_NULL); return -1; } tmpr_loc = moo->c->tok.loc; if (compile_block_temporaries(moo) <= -1) return -1; /* this is a block-local temporary count including arguments */ block_tmpr_count = md->tmpr_count - saved_tmpr_count; if (block_tmpr_count > MAX_CODE_NBLKTMPRS) { moo_setsynerr (moo, MOO_SYNERR_BLKTMPRFLOOD, &tmpr_loc, MOO_NULL); return -1; } /* store the accumulated number of temporaries for the current block. * block depth is not raised as it's not entering a new block but * updating the temporaries count for the current block. */ if (store_tmpr_count_for_block(moo, md->tmpr_count) <= -1) return -1; if (emit_double_param_instruction(moo, BCODE_MAKE_BLOCK, block_arg_count, md->tmpr_count/*block_tmpr_count*/, &block_loc) <= -1) return -1; /* insert dummy instructions before replacing them with a jump instruction */ jump_inst_pos = md->code.len; /* specifying MAX_CODE_JUMP causes emit_single_param_instruction() to * produce the long jump instruction (BCODE_JUMP_FORWARD) */ if (emit_single_param_instruction(moo, BCODE_JUMP_FORWARD, MAX_CODE_JUMP, &block_loc) <= -1) return -1; /* compile statements inside a block */ code_start = md->code.len; if (emit_byte_instruction(moo, BCODE_PUSH_NIL, TOKEN_LOC(moo)) <= -1) return -1; if (TOKEN_TYPE(moo) != MOO_IOTOK_RBRACK) { moo_oow_t pop_stacktop_pos; pop_stacktop_pos = md->code.len; if (emit_byte_instruction(moo, BCODE_POP_STACKTOP, TOKEN_LOC(moo)) <= -1) return -1; do { int n; if (TOKEN_TYPE(moo) == MOO_IOTOK_EOF) { moo_setsynerr (moo, MOO_SYNERR_RBRACK, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } n = compile_block_statement(moo); if (n <= -1) return -1; if (n == 8888) { /* compile_block_statement() processed non-statement item like a jump label. */ MOO_ASSERT (moo, md->_label != MOO_NULL); if (TOKEN_TYPE(moo) == MOO_IOTOK_RBRACK) { /* the last label inside [] must be followed by a valid statement */ moo_oocs_t labname; labname.ptr = (moo_ooch_t*)(md->_label + 1); labname.len = moo_count_oocstr(labname.ptr); moo_setsynerrbfmt (moo, MOO_SYNERR_LABELATEND, &md->_label->loc, &labname, "label at end of square bracketed block"); return -1; } } else if (n == 7777) { /* goto statement - * the goto statement looks like a normal statement, but it never pushes a value. * so no pop_stacktop instruction needs to get injected */ if (TOKEN_TYPE(moo) == MOO_IOTOK_RBRACK) { /* eliminate BCODE_POP_STACKTOP produced in the else block below * becuase the non-statement item is the last item before the closing bracket */ if (pop_stacktop_pos != INVALID_IP) { eliminate_instructions (moo, pop_stacktop_pos, pop_stacktop_pos); pop_stacktop_pos = INVALID_IP; } break; } else if (TOKEN_TYPE(moo) == MOO_IOTOK_PERIOD) { GET_TOKEN (moo); if (TOKEN_TYPE(moo) == MOO_IOTOK_RBRACK) break; } else { moo_setsynerr (moo, MOO_SYNERR_RBRACK, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } } else { /* a proper statement has been processed in compile_block_statemnt */ pop_stacktop_pos = md->code.len; if (emit_byte_instruction(moo, BCODE_POP_STACKTOP, TOKEN_LOC(moo)) <= -1) return -1; if (TOKEN_TYPE(moo) == MOO_IOTOK_RBRACK) { eliminate_instructions(moo, pop_stacktop_pos, pop_stacktop_pos); pop_stacktop_pos = INVALID_IP; break; } else if (TOKEN_TYPE(moo) == MOO_IOTOK_PERIOD) { GET_TOKEN (moo); if (TOKEN_TYPE(moo) == MOO_IOTOK_RBRACK) { eliminate_instructions(moo, pop_stacktop_pos, pop_stacktop_pos); pop_stacktop_pos = INVALID_IP; break; } } else { moo_setsynerr (moo, MOO_SYNERR_RBRACK, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } } } while (1); MOO_ASSERT (moo, md->code.len > code_start); if (md->code.len - code_start >= 2 && md->code.ptr[code_start] == BCODE_PUSH_NIL && md->code.ptr[code_start + 1] == BCODE_POP_STACKTOP) { /* elminnate the block prologue */ eliminate_instructions(moo, code_start, code_start + 1); } } if (emit_byte_instruction(moo, BCODE_RETURN_FROM_BLOCK, TOKEN_LOC(moo)) <= -1) return -1; if (patch_forward_jump_instruction(moo, jump_inst_pos, md->code.len) <= -1) { moo_setsynerrbfmt (moo, MOO_SYNERR_INSTFLOOD, &block_loc, MOO_NULL, "unable to patch block jump"); return -1; } /* restore the temporary count */ md->tmprs.len = saved_tmprs_len; md->tmpr_count = saved_tmpr_count; if (md->loop) { MOO_ASSERT (moo, md->loop->blkcount > 0); md->loop->blkcount--; } GET_TOKEN (moo); /* read the next token after ] */ return 0; } static int add_to_byte_array_literal_buffer (moo_t* moo, moo_oob_t b) { if (moo->c->balit.count >= moo->c->balit.capa) { moo_oob_t* tmp; moo_oow_t new_capa; new_capa = MOO_ALIGN (moo->c->balit.count + 1, BALIT_BUFFER_ALIGN); tmp = (moo_oob_t*)moo_reallocmem(moo, moo->c->balit.ptr, new_capa * MOO_SIZEOF(*tmp)); if (!tmp) return -1; moo->c->balit.capa = new_capa; moo->c->balit.ptr = tmp; } /* TODO: overflow check of moo->c->balit.count itself */ moo->c->balit.ptr[moo->c->balit.count++] = b; return 0; } static MOO_INLINE int add_to_array_literal_buffer (moo_t* moo, moo_oop_t item) { return add_oop_to_oopbuf(moo, &moo->c->arlit, item); } static MOO_INLINE int find_in_array_literal_buffer (moo_t* moo, moo_oow_t start, moo_oow_t step, moo_oop_t item, moo_oow_t* index) { return find_oop_in_oopbuf(moo, &moo->c->arlit, start, step, item, index); } static int read_byte_array_literal (moo_t* moo, int rdonly, moo_oop_t* xlit) { moo_ooi_t tmp; moo_oop_t ba; moo_oow_t saved_balit_count; int comma_used = -1; /* unknown */ saved_balit_count = moo->c->balit.count; GET_TOKEN_GOTO (moo, oops); /* skip #[ and read the next token */ while (TOKEN_TYPE(moo) == MOO_IOTOK_INTLIT || TOKEN_TYPE(moo) == MOO_IOTOK_RADINTLIT || TOKEN_TYPE(moo) == MOO_IOTOK_CHARLIT || TOKEN_TYPE(moo) == MOO_IOTOK_STRLIT) { /* TODO: check if the number is an integer */ if (TOKEN_TYPE(moo) == MOO_IOTOK_STRLIT) { moo_oow_t i; for (i = 0; i < TOKEN_NAME_LEN(moo); i++) { tmp = TOKEN_NAME_PTR(moo)[i]; if (tmp < 0 || tmp > 0xFF) { moo_setsynerr (moo, MOO_SYNERR_BYTERANGE, TOKEN_LOC(moo), TOKEN_NAME(moo)); goto oops; } if (add_to_byte_array_literal_buffer(moo, tmp) <= -1) goto oops; } } else { if (TOKEN_TYPE(moo) == MOO_IOTOK_CHARLIT) { /* accept a character literal inside a byte array literal */ tmp = TOKEN_NAME_PTR(moo)[0]; } else if (string_to_smooi(moo, TOKEN_NAME(moo), TOKEN_TYPE(moo) == MOO_IOTOK_RADINTLIT, &tmp) <= -1) { /* the token reader reads a valid token. no other errors * than the range error must not occur */ MOO_ASSERT (moo, moo->errnum == MOO_ERANGE); /* if the token is out of the SMOOI range, it's too big or * to small to be a byte */ moo_setsynerr (moo, MOO_SYNERR_BYTERANGE, TOKEN_LOC(moo), TOKEN_NAME(moo)); goto oops; } if (tmp < 0 || tmp > 0xFF) { moo_setsynerr (moo, MOO_SYNERR_BYTERANGE, TOKEN_LOC(moo), TOKEN_NAME(moo)); goto oops; } if (add_to_byte_array_literal_buffer(moo, tmp) <= -1) goto oops; } GET_TOKEN_GOTO (moo, oops); if (comma_used == -1) { /* check if a comma has been used after the first element */ if (TOKEN_TYPE(moo) == MOO_IOTOK_COMMA) { comma_used = 1; GET_TOKEN_GOTO (moo, oops); } else { comma_used = 0; } } else if (comma_used == 0) { /* a comma is not expected */ if (TOKEN_TYPE(moo) == MOO_IOTOK_COMMA) { break; } } else if (comma_used == 1) { /* a comma is expected */ if (TOKEN_TYPE(moo) == MOO_IOTOK_RBRACK) break; if (TOKEN_TYPE(moo) != MOO_IOTOK_COMMA) { moo_setsynerr (moo, MOO_SYNERR_COMMA, TOKEN_LOC(moo), TOKEN_NAME(moo)); goto oops; } GET_TOKEN_GOTO (moo, oops); if (TOKEN_TYPE(moo) == MOO_IOTOK_COMMA || TOKEN_TYPE(moo) == MOO_IOTOK_RBRACK) { moo_setsynerr (moo, MOO_SYNERR_LITERAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); goto oops; } } } if (TOKEN_TYPE(moo) != MOO_IOTOK_RBRACK) { moo_setsynerr (moo, MOO_SYNERR_RBRACK, TOKEN_LOC(moo), TOKEN_NAME(moo)); goto oops; } ba = moo_instantiate(moo, moo->_byte_array, &moo->c->balit.ptr[saved_balit_count], moo->c->balit.count - saved_balit_count); if (!ba) goto oops; if (rdonly) { MOO_ASSERT (moo, MOO_OOP_IS_POINTER(ba)); MOO_OBJ_SET_FLAGS_RDONLY (ba, 1); } *xlit = ba; moo->c->balit.count = saved_balit_count; return 0; oops: moo->c->balit.count = saved_balit_count; return -1; } static int read_array_literal (moo_t* moo, int rdonly, moo_oop_t* xlit) { moo_oop_t lit, a; moo_oow_t i, j, saved_arlit_count; int comma_used = -1; /* unknown */ saved_arlit_count = moo->c->arlit.count; GET_TOKEN_GOTO (moo, oops); /* skip #( */ if (TOKEN_TYPE(moo) == MOO_IOTOK_EOF) { moo_setsynerr (moo, MOO_SYNERR_RPAREN, TOKEN_LOC(moo), TOKEN_NAME(moo)); goto oops; } else if (TOKEN_TYPE(moo) != MOO_IOTOK_RPAREN) { do { lit = token_to_literal(moo, rdonly); if (!lit || add_to_array_literal_buffer(moo, lit) <= -1) goto oops; GET_TOKEN_GOTO (moo, oops); if (TOKEN_TYPE(moo) == MOO_IOTOK_EOF || TOKEN_TYPE(moo) == MOO_IOTOK_RPAREN) break; if (comma_used == -1) { /* check if a comma has been used after the first element */ if (TOKEN_TYPE(moo) == MOO_IOTOK_COMMA) { comma_used = 1; goto literal_expected_after_comma; } else { comma_used = 0; if (TOKEN_TYPE(moo) == MOO_IOTOK_RPAREN || TOKEN_TYPE(moo) == MOO_IOTOK_EOF) break; } } else if (comma_used == 0) { /* a comma is not expected. stop if EOF or ) is encountered */ if (TOKEN_TYPE(moo) == MOO_IOTOK_COMMA || TOKEN_TYPE(moo) == MOO_IOTOK_RPAREN || TOKEN_TYPE(moo) == MOO_IOTOK_EOF) break; } else if (comma_used == 1) { /* a comma is expected */ if (TOKEN_TYPE(moo) != MOO_IOTOK_COMMA) { moo_setsynerr (moo, MOO_SYNERR_COMMA, TOKEN_LOC(moo), TOKEN_NAME(moo)); goto oops; } literal_expected_after_comma: GET_TOKEN_GOTO (moo, oops); if (TOKEN_TYPE(moo) == MOO_IOTOK_COMMA || TOKEN_TYPE(moo) == MOO_IOTOK_RPAREN || TOKEN_TYPE(moo) == MOO_IOTOK_EOF) { moo_setsynerr (moo, MOO_SYNERR_LITERAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); goto oops; } } } while (1); if (TOKEN_TYPE(moo) != MOO_IOTOK_RPAREN) { moo_setsynerr (moo, MOO_SYNERR_RPAREN, TOKEN_LOC(moo), TOKEN_NAME(moo)); goto oops; } } a = moo_instantiate(moo, moo->_array, MOO_NULL, moo->c->arlit.count - saved_arlit_count); if (!a) goto oops; for (i = saved_arlit_count, j = 0; i < moo->c->arlit.count; i++, j++) { MOO_STORE_OOP (moo, MOO_OBJ_GET_OOP_PTR(a, j), moo->c->arlit.ptr[i]); } if (rdonly) { MOO_ASSERT (moo, MOO_OOP_IS_POINTER(a)); MOO_OBJ_SET_FLAGS_RDONLY (a, 1); } *xlit = a; moo->c->arlit.count = saved_arlit_count; return 0; oops: moo->c->arlit.count = saved_arlit_count; return -1; } static int compile_byte_array_literal (moo_t* moo) { moo_oop_t lit; moo_oow_t index; moo_ioloc_t start_loc; MOO_ASSERT (moo, moo->c->balit.count == 0); MOO_ASSERT (moo, TOKEN_TYPE(moo) == MOO_IOTOK_HASHBRACK); start_loc = *TOKEN_LOC(moo); if (read_byte_array_literal(moo, 1, &lit) <= -1 || add_literal(moo, lit, &index) <= -1 || emit_single_param_instruction(moo, BCODE_PUSH_LITERAL_0, index, &start_loc) <= -1) return -1; GET_TOKEN (moo); return 0; } static int compile_array_literal (moo_t* moo) { moo_oop_t lit; moo_oow_t index; moo_ioloc_t start_loc; MOO_ASSERT (moo, moo->c->arlit.count == 0); MOO_ASSERT (moo, TOKEN_TYPE(moo) == MOO_IOTOK_HASHPAREN); start_loc = *TOKEN_LOC(moo); if (read_array_literal(moo, 1, &lit) <= -1 || add_literal(moo, lit, &index) <= -1 || emit_single_param_instruction(moo, BCODE_PUSH_LITERAL_0, index, &start_loc) <= -1) return -1; GET_TOKEN (moo); return 0; } static int _compile_array_expression (moo_t* moo, int closer_token, int bcode_make, int bcode_pop_into) { moo_method_data_t* md = get_cunit_method_data(moo); moo_oow_t maip; moo_ioloc_t start_loc; MOO_ASSERT (moo, TOKEN_TYPE(moo) == MOO_IOTOK_DHASHPAREN || TOKEN_TYPE(moo) == MOO_IOTOK_DHASHBRACK); start_loc = *TOKEN_LOC(moo); maip = md->code.len; if (emit_single_param_instruction(moo, bcode_make, 0, &start_loc) <= -1) return -1; GET_TOKEN (moo); /* read a token after ##( or ##[ */ if (TOKEN_TYPE(moo) != closer_token) { moo_oow_t index; index = 0; do { if (compile_method_expression(moo, 0) <= -1) return -1; if (emit_single_param_instruction(moo, bcode_pop_into, index, TOKEN_LOC(moo)) <= -1) return -1; index++; if (index > MAX_CODE_PARAM) { moo_setsynerr (moo, MOO_SYNERR_ARREXPFLOOD, &start_loc, MOO_NULL); return -1; } if (TOKEN_TYPE(moo) == closer_token) break; if (TOKEN_TYPE(moo) != MOO_IOTOK_COMMA) { moo_setsynerr (moo, MOO_SYNERR_COMMA, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); } while (1); /* TODO: devise a double_param MAKE_ARRAY to increase the number of elementes supported... */ /* patch the MAKE_ARRAY instruction */ #if (MOO_BCODE_LONG_PARAM_SIZE == 2) md->code.ptr[maip + 1] = index >> 8; md->code.ptr[maip + 2] = index & 0xFF; #else md->code.ptr[maip + 1] = index; #endif } GET_TOKEN (moo); /* read a token after ( or ] */ return 0; } static int compile_array_expression (moo_t* moo) { return _compile_array_expression(moo, MOO_IOTOK_RPAREN, BCODE_MAKE_ARRAY, BCODE_POP_INTO_ARRAY); } static int compile_bytearray_expression (moo_t* moo) { return _compile_array_expression (moo, MOO_IOTOK_RBRACK, BCODE_MAKE_BYTEARRAY, BCODE_POP_INTO_BYTEARRAY); } static int compile_dictionary_expression (moo_t* moo) { moo_method_data_t* md = get_cunit_method_data(moo); moo_oow_t mdip; moo_ioloc_t start_loc; MOO_ASSERT (moo, TOKEN_TYPE(moo) == MOO_IOTOK_DHASHBRACE); start_loc = *TOKEN_LOC(moo); GET_TOKEN (moo); /* read a token after ##{ */ mdip = md->code.len; if (emit_single_param_instruction(moo, BCODE_MAKE_DICTIONARY, 0, &start_loc) <= -1) return -1; if (TOKEN_TYPE(moo) != MOO_IOTOK_RBRACE) { moo_oow_t count; count = 0; do { if (compile_method_expression(moo, 0) <= -1 || emit_byte_instruction(moo, BCODE_POP_INTO_DICTIONARY, TOKEN_LOC(moo)) <= -1) return -1; count++; if (TOKEN_TYPE(moo) == MOO_IOTOK_RBRACE) break; if (TOKEN_TYPE(moo) == MOO_IOTOK_COMMA) { GET_TOKEN(moo); } } while (1); /* count is just a hint unlike in array */ if (count > MAX_CODE_PARAM) count = MAX_CODE_PARAM; /* patch the MAKE_DICTIONARY instruction */ #if (MOO_BCODE_LONG_PARAM_SIZE == 2) md->code.ptr[mdip + 1] = count >> 8; md->code.ptr[mdip + 2] = count & 0xFF; #else md->code.ptr[mdip + 1] = count; #endif } GET_TOKEN (moo); return 0; } static int compile_expression_primary (moo_t* moo, const moo_oocs_t* ident, const moo_ioloc_t* ident_loc, int ident_dotted, int* to_super) { /* * expression-primary := identifier | literal | block-constructor | ( "(" method-expression ")" ) */ moo_method_data_t* md = get_cunit_method_data(moo); var_info_t var; int read_next_token = 0; moo_oow_t index; *to_super = 0; if (ident) { /* the caller has read the identifier and the next word */ handle_ident: if (get_variable_info(moo, ident, ident_loc, ident_dotted, &var) <= -1) return -1; switch (var.type) { case VAR_ARGUMENT: case VAR_TEMPORARY: { #if defined(MOO_USE_CTXTEMPVAR) if (md->blk_depth > 0) { moo_oow_t i; /* if a temporary variable is accessed inside a block, * use a special instruction to indicate it */ MOO_ASSERT (moo, var.pos < md->blk_tmprcnt[md->blk_depth]); for (i = md->blk_depth; i > 0; i--) { if (var.pos >= md->blk_tmprcnt[i - 1]) { if (emit_double_param_instruction(moo, BCODE_PUSH_CTXTEMPVAR_0, md->blk_depth - i, var.pos - md->blk_tmprcnt[i - 1], ident_loc) <= -1) return -1; goto temporary_done; } } } #endif if (emit_single_param_instruction(moo, BCODE_PUSH_TEMPVAR_0, var.pos, ident_loc) <= -1) return -1; temporary_done: break; } case VAR_INSTANCE: case VAR_CLASSINST: if (emit_single_param_instruction(moo, BCODE_PUSH_INSTVAR_0, var.pos, ident_loc) <= -1) return -1; break; case VAR_CLASS: /* get_variable_info must not set var.cls to MOO_NULL * because the class object pointer should be available * when a method definition is compiled */ MOO_ASSERT (moo, var.cls != MOO_NULL); if (add_literal(moo, (moo_oop_t)var.cls, &index) <= -1 || emit_double_param_instruction(moo, BCODE_PUSH_OBJVAR_0, var.pos, index, ident_loc) <= -1) return -1; break; case VAR_GLOBAL: /* [NOTE] * the association object pointed to by a system dictionary * is stored into the literal frame. so the system dictionary * must not migrate the value of the association to a new * association when it rehashes the entire dictionary. * If the association entry is deleted from the dictionary, * the code compiled before deletion will still access * the deleted association */ if (add_literal(moo, (moo_oop_t)var.u.gbl, &index) <= -1 || emit_single_param_instruction(moo, BCODE_PUSH_OBJECT_0, index, ident_loc) <= -1) return -1; break; default: moo_seterrnum (moo, MOO_EINTERN); return -1; } if (read_next_token) GET_TOKEN (moo); } else { switch (TOKEN_TYPE(moo)) { case MOO_IOTOK_IDENT_DOTTED: case MOO_IOTOK_IDENT: ident_dotted = (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT_DOTTED); ident = TOKEN_NAME(moo); ident_loc = TOKEN_LOC(moo); read_next_token = 1; goto handle_ident; case MOO_IOTOK_SELF: if (emit_byte_instruction(moo, BCODE_PUSH_RECEIVER, TOKEN_LOC(moo)) <= -1) return -1; GET_TOKEN (moo); break; case MOO_IOTOK_SUPER: if (moo->c->cunit->cunit_type == MOO_CUNIT_INTERFACE) { /* i don't allow super in an interface method * message sending to super requires the owning class * of an active method. a method taken by a class from * an interface points to the interface in the owner field. * for now, it's best not to allow it. * see moo_findmethod() in exec.c */ moo_setsynerrbfmt (moo, MOO_SYNERR_SUPERINACC, TOKEN_LOC(moo), TOKEN_NAME(moo), "super inaccessible in interface method"); return -1; } if (emit_byte_instruction(moo, BCODE_PUSH_RECEIVER, TOKEN_LOC(moo)) <= -1) return -1; GET_TOKEN (moo); *to_super = 1; break; case MOO_IOTOK_NIL: if (emit_byte_instruction(moo, BCODE_PUSH_NIL, TOKEN_LOC(moo)) <= -1) return -1; GET_TOKEN (moo); break; case MOO_IOTOK_TRUE: if (emit_byte_instruction(moo, BCODE_PUSH_TRUE, TOKEN_LOC(moo)) <= -1) return -1; GET_TOKEN (moo); break; case MOO_IOTOK_FALSE: if (emit_byte_instruction(moo, BCODE_PUSH_FALSE, TOKEN_LOC(moo)) <= -1) return -1; GET_TOKEN (moo); break; case MOO_IOTOK_ERRLIT: { moo_oop_t tmp; tmp = string_to_error(moo, TOKEN_NAME(moo), TOKEN_LOC(moo)); if (!tmp) return -1; if (add_literal(moo, tmp, &index) <= -1 || emit_single_param_instruction(moo, BCODE_PUSH_LITERAL_0, index, TOKEN_LOC(moo)) <= -1) return -1; GET_TOKEN (moo); break; } case MOO_IOTOK_SMPTRLIT: { moo_oop_t tmp; tmp = string_to_ptr(moo, TOKEN_NAME(moo), TOKEN_LOC(moo)); if (!tmp) return -1; if (add_literal(moo, tmp, &index) <= -1 || emit_single_param_instruction(moo, BCODE_PUSH_LITERAL_0, index, TOKEN_LOC(moo)) <= -1) return -1; GET_TOKEN (moo); break; } case MOO_IOTOK_THIS_CONTEXT: if (emit_byte_instruction(moo, BCODE_PUSH_CONTEXT, TOKEN_LOC(moo)) <= -1) return -1; GET_TOKEN (moo); break; case MOO_IOTOK_THIS_PROCESS: if (emit_byte_instruction(moo, BCODE_PUSH_PROCESS, TOKEN_LOC(moo)) <= -1) return -1; GET_TOKEN (moo); break; case MOO_IOTOK_SELFNS: if (emit_byte_instruction(moo, BCODE_PUSH_RECEIVER_NS, TOKEN_LOC(moo)) <= -1) return -1; GET_TOKEN (moo); break; case MOO_IOTOK_CHARLIT: MOO_ASSERT (moo, TOKEN_NAME_LEN(moo) == 1); if (emit_push_character_literal(moo, TOKEN_NAME_PTR(moo)[0], TOKEN_LOC(moo)) <= -1) return -1; GET_TOKEN (moo); break; case MOO_IOTOK_INTLIT: case MOO_IOTOK_RADINTLIT: { /* TODO: floating pointer number */ /* TODO: other types of numbers, etc */ moo_oop_t tmp; tmp = string_to_int(moo, TOKEN_NAME(moo), TOKEN_TYPE(moo) == MOO_IOTOK_RADINTLIT); if (!tmp) return -1; if (MOO_OOP_IS_SMOOI(tmp)) { if (emit_push_smooi_literal(moo, MOO_OOP_TO_SMOOI(tmp), TOKEN_LOC(moo)) <= -1) return -1; } else { if (add_literal(moo, tmp, &index) <= -1 || emit_single_param_instruction(moo, BCODE_PUSH_LITERAL_0, index, TOKEN_LOC(moo)) <= -1) return -1; } GET_TOKEN (moo); break; } case MOO_IOTOK_FPDECLIT: case MOO_IOTOK_SCALEDFPDECLIT: { moo_oop_t tmp; tmp = string_to_fpdec(moo, TOKEN_NAME(moo), TOKEN_TYPE(moo) == MOO_IOTOK_SCALEDFPDECLIT); if (!tmp) return -1; if (add_literal(moo, tmp, &index) <= -1 || emit_single_param_instruction(moo, BCODE_PUSH_LITERAL_0, index, TOKEN_LOC(moo)) <= -1) return -1; GET_TOKEN (moo); break; } case MOO_IOTOK_SYMLIT: if (add_symbol_literal(moo, TOKEN_NAME(moo), 1, &index) <= -1 || emit_single_param_instruction(moo, BCODE_PUSH_LITERAL_0, index, TOKEN_LOC(moo)) <= -1) return -1; GET_TOKEN (moo); break; case MOO_IOTOK_STRLIT: if (add_string_literal(moo, TOKEN_NAME(moo), &index) <= -1 || emit_single_param_instruction(moo, BCODE_PUSH_LITERAL_0, index, TOKEN_LOC(moo)) <= -1) return -1; GET_TOKEN (moo); break; case MOO_IOTOK_BYTEARRAYLIT: /* B"xxxxx". see MOO_IOTOK_HASHBRACK below for comparision */ if (add_byte_array_literal(moo, TOKEN_NAME(moo), &index) <= -1 || emit_single_param_instruction(moo, BCODE_PUSH_LITERAL_0, index, TOKEN_LOC(moo)) <= -1) return -1; GET_TOKEN (moo); break; case MOO_IOTOK_HASHPAREN: /* #( */ /*GET_TOKEN (moo);*/ if (compile_array_literal(moo) <= -1) return -1; break; case MOO_IOTOK_HASHBRACK: /* #[ */ /*GET_TOKEN (moo);*/ if (compile_byte_array_literal(moo) <= -1) return -1; break; #if 0 /* TODO: */ case MOO_IOTOK_HASHBRACE: /* #{ */ if (compile_dictionary_literal(moo) <= -1) return -1; break; #endif case MOO_IOTOK_DHASHPAREN: /* ##( */ if (compile_array_expression(moo) <= -1) return -1; break; case MOO_IOTOK_DHASHBRACK: /* ##[ */ if (compile_bytearray_expression(moo) <= -1) return -1; break; case MOO_IOTOK_DHASHBRACE: /* ##{ */ if (compile_dictionary_expression(moo) <= -1) return -1; break; case MOO_IOTOK_LBRACK: /* [ */ { int n; moo_oow_t cur_blk_id; /*GET_TOKEN (moo);*/ if (store_tmpr_count_for_block(moo, md->tmpr_count) <= -1) return -1; cur_blk_id = md->blk_id; /* save the current block id */ md->blk_id = md->blk_idseq++; /* allocate a new block id for compile_block_expression() */ md->blk_depth++; /* * md->tmpr_count[0] contains the number of temporaries for a method. * md->tmpr_count[1] contains the number of temporaries for the block plus the containing method. * ... * md->tmpr_count[n] contains the number of temporaries for the block plus all containing method and blocks. */ n = compile_block_expression(moo); md->blk_depth--; md->blk_id = cur_blk_id; /* put back the saved block id */ if (n <= -1) return -1; break; } case MOO_IOTOK_LPAREN: GET_TOKEN (moo); if (compile_method_expression(moo, 0) <= -1) return -1; if (TOKEN_TYPE(moo) != MOO_IOTOK_RPAREN) { moo_setsynerr (moo, MOO_SYNERR_RPAREN, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); break; default: moo_setsynerr (moo, MOO_SYNERR_PRIMARYINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } } return 0; } static moo_oob_t send_message_cmd[] = { BCODE_SEND_MESSAGE_0, BCODE_SEND_MESSAGE_TO_SUPER_0 }; static int compile_unary_message (moo_t* moo, int to_super) { moo_method_data_t* md = get_cunit_method_data(moo); moo_oow_t index; moo_oow_t nargs; moo_ioloc_t sel_loc; MOO_ASSERT (moo, TOKEN_TYPE(moo) == MOO_IOTOK_IDENT); do { sel_loc = *TOKEN_LOC(moo); nargs = 0; if (add_symbol_literal(moo, TOKEN_NAME(moo), 0, &index) <= -1) return -1; GET_TOKEN (moo); if (TOKEN_TYPE(moo) == MOO_IOTOK_LPAREN) { /* parameterized procedure call */ GET_TOKEN(moo); if (TOKEN_TYPE(moo) != MOO_IOTOK_RPAREN) { do { int n; moo_oow_t cur_blk_id; /* this argument is not a real block. but change the block id * to prevent 'goto' from jumping out of the argument expression */ cur_blk_id = md->blk_id; md->blk_id = md->blk_idseq++; n = compile_method_expression(moo, 0); md->blk_id = cur_blk_id; if (n <= -1) return -1; nargs++; if (TOKEN_TYPE(moo) == MOO_IOTOK_RPAREN) break; if (TOKEN_TYPE(moo) != MOO_IOTOK_COMMA) { moo_setsynerr (moo, MOO_SYNERR_COMMA, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN(moo); } while (1); } GET_TOKEN(moo); /* [NOTE] since the actual method may not be known at the compile time, * i can't check if nargs will match the number of arguments * expected by the method */ } if (emit_double_param_instruction(moo, send_message_cmd[to_super], nargs, index, &sel_loc) <= -1) return -1; /* In 'super new xxx', xxx is sent to the object returned by new. * that means it is not sent to 'super' */ to_super = 0; } while (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT); return 0; } static int compile_binary_message (moo_t* moo, int to_super) { /* * binary-message := binary-selector binary-argument * binary-argument := expression-primary unary-message* */ moo_method_data_t* md = get_cunit_method_data(moo); moo_oow_t index; int to_super2; moo_oocs_t binsel; moo_oow_t saved_binsels_len, binsel_offset; moo_ioloc_t sel_loc; moo_oow_t cur_blk_id; int n; MOO_ASSERT (moo, TOKEN_TYPE(moo) == MOO_IOTOK_BINSEL); do { sel_loc = *TOKEN_LOC(moo); binsel = *TOKEN_NAME(moo); saved_binsels_len = md->binsels.len; if (clone_binary_selector(moo, &binsel, &binsel_offset) <= -1) goto oops; GET_TOKEN (moo); /* this argument expression is not a real block. but change the block id * to prevent 'goto' from jumping out of the argument expression */ cur_blk_id = md->blk_id; md->blk_id = md->blk_idseq++; n = compile_expression_primary(moo, MOO_NULL, MOO_NULL, 0, &to_super2); md->blk_id = cur_blk_id; if (n <= -1) goto oops; if (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT && compile_unary_message(moo, to_super2) <= -1) goto oops; /* update the pointer to the cloned selector now * to be free from reallocation risk for the recursive call * to compile_expression_primary(). */ binsel.ptr = &md->binsels.ptr[binsel_offset]; if (add_symbol_literal(moo, &binsel, 0, &index) <= -1 || emit_double_param_instruction(moo, send_message_cmd[to_super], 1, index, &sel_loc) <= -1) goto oops; to_super = 0; /* In super + 2 - 3, '-' is sent to the return value of '+', not to super */ md->binsels.len = saved_binsels_len; } while (TOKEN_TYPE(moo) == MOO_IOTOK_BINSEL); return 0; oops: md->binsels.len = saved_binsels_len; return -1; } static int compile_keyword_message (moo_t* moo, int to_super) { /* * keyword-message := (keyword keyword-argument)+ * keyword-argument := expression-primary unary-message* binary-message* */ moo_method_data_t* md = get_cunit_method_data(moo); moo_oow_t index; int to_super2; moo_oocs_t kw, kwsel; moo_ioloc_t saved_kwsel_loc; moo_oow_t saved_kwsel_len; moo_oow_t kw_offset; moo_oow_t nargs = 0; moo_oow_t cur_blk_id; int n; saved_kwsel_loc = moo->c->tok.loc; saved_kwsel_len = md->kwsels.len; /* TODO: optimization for ifTrue: ifFalse: whileTrue: whileFalse .. */ do { kw = *TOKEN_NAME(moo); if (clone_keyword(moo, &kw, &kw_offset) <= -1) goto oops; GET_TOKEN (moo); /* this argument expression is not a real block. but change the block id * to prevent 'goto' from jumping out of the argument expression */ cur_blk_id = md->blk_id; md->blk_id = md->blk_idseq++; n = compile_expression_primary(moo, MOO_NULL, MOO_NULL, 0, &to_super2); md->blk_id = cur_blk_id; if (n <= -1) goto oops; if (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT && compile_unary_message(moo, to_super2) <= -1) goto oops; if (TOKEN_TYPE(moo) == MOO_IOTOK_BINSEL && compile_binary_message(moo, to_super2) <= -1) goto oops; kw.ptr = &md->kwsels.ptr[kw_offset]; if (nargs >= MAX_CODE_NARGS) { /* 'kw' points to only one segment of the full keyword message. * if it parses an expression like 'aBlock value: 10 with: 20', * 'kw' may point to 'value:' or 'with:'. */ moo_setsynerr (moo, MOO_SYNERR_ARGFLOOD, &saved_kwsel_loc, &kw); goto oops; } nargs++; } while (TOKEN_TYPE(moo) == MOO_IOTOK_KEYWORD) /* loop */; kwsel.ptr = &md->kwsels.ptr[saved_kwsel_len]; kwsel.len = md->kwsels.len - saved_kwsel_len; if (add_symbol_literal(moo, &kwsel, 0, &index) <= -1 || emit_double_param_instruction(moo, send_message_cmd[to_super], nargs, index, &saved_kwsel_loc) <= -1) goto oops; md->kwsels.len = saved_kwsel_len; return 0; oops: md->kwsels.len = saved_kwsel_len; return -1; } static int compile_message_expression (moo_t* moo, int to_super) { /* * message-expression := single-message cascaded-message * single-message := * (unary-message+ binary-message* keyword-message?) | * (binary-message+ keyword-message?) | * keyword-message * * keyword-message := (keyword keyword-argument)+ * keyword-argument := expression-primary unary-message* binary-message* * binary-message := binary-selector binary-argument * binary-argument := expression-primary unary-message* * unary-message := unary-selector * cascaded-message := (";" single-message)* */ moo_method_data_t* md = get_cunit_method_data(moo); moo_oow_t noop_pos; do { switch (TOKEN_TYPE(moo)) { case MOO_IOTOK_IDENT: /* insert NOOP to change to DUP_STACKTOP if there is a * cascaded message */ noop_pos = md->code.len; if (emit_byte_instruction(moo, BCODE_NOOP, TOKEN_LOC(moo)) <= -1) return -1; if (compile_unary_message(moo, to_super) <= -1) return -1; if (TOKEN_TYPE(moo) == MOO_IOTOK_BINSEL) { MOO_ASSERT (moo, md->code.len > noop_pos); /*MOO_MEMMOVE (&md->code.ptr[noop_pos], &md->code.ptr[noop_pos + 1], md->code.len - noop_pos - 1); md->code.len--;*/ /* eliminate the NOOP instruction */ eliminate_instructions (moo, noop_pos, noop_pos); noop_pos = md->code.len; if (emit_byte_instruction(moo, BCODE_NOOP, TOKEN_LOC(moo)) <= -1) return -1; /* to_super is reset to 0 because a unary message * has been sent to super and this binary message * is following the unary message. * for instance, in "super abc + 10", + is sent * to the 'self' which is the result of super abc */ if (compile_binary_message(moo, 0/*to_super*/) <= -1) return -1; } if (TOKEN_TYPE(moo) == MOO_IOTOK_KEYWORD) { MOO_ASSERT (moo, md->code.len > noop_pos); /*MOO_MEMMOVE (&md->code.ptr[noop_pos], &md->code.ptr[noop_pos + 1], md->code.len - noop_pos - 1); md->code.len--;*/ /* eliminate the NOOP instruction */ eliminate_instructions (moo, noop_pos, noop_pos); noop_pos = md->code.len; if (emit_byte_instruction(moo, BCODE_NOOP, TOKEN_LOC(moo)) <= -1) return -1; /* don't pass to_super. pass 0 as it can't be the * first message after 'super' */ if (compile_keyword_message(moo, 0/*to_super*/) <= -1) return -1; } break; case MOO_IOTOK_BINSEL: noop_pos = md->code.len; if (emit_byte_instruction(moo, BCODE_NOOP, TOKEN_LOC(moo)) <= -1) return -1; if (compile_binary_message(moo, to_super) <= -1) return -1; if (TOKEN_TYPE(moo) == MOO_IOTOK_KEYWORD) { MOO_ASSERT (moo, md->code.len > noop_pos); /*MOO_MEMMOVE (&md->code.ptr[noop_pos], &md->code.ptr[noop_pos + 1], md->code.len - noop_pos - 1); md->code.len--;*/ /* eliminate the NOOP instruction */ eliminate_instructions (moo, noop_pos, noop_pos); noop_pos = md->code.len; if (emit_byte_instruction(moo, BCODE_NOOP, TOKEN_LOC(moo)) <= -1) return -1; /* don't pass to_super. pass 0 as it can't be the * first message after 'super' */ if (compile_keyword_message(moo, 0/*to_super*/) <= -1) return -1; } break; case MOO_IOTOK_KEYWORD: noop_pos = md->code.len; if (emit_byte_instruction(moo, BCODE_NOOP, TOKEN_LOC(moo)) <= -1) return -1; if (compile_keyword_message(moo, to_super) <= -1) return -1; break; default: goto done; } if (TOKEN_TYPE(moo) == MOO_IOTOK_SEMICOLON) { md->code.ptr[noop_pos] = BCODE_DUP_STACKTOP; if (emit_byte_instruction(moo, BCODE_POP_STACKTOP, TOKEN_LOC(moo)) <= -1) return -1; GET_TOKEN(moo); } else { MOO_ASSERT (moo, md->code.len > noop_pos); /*MOO_MEMMOVE (&md->code.ptr[noop_pos], &md->code.ptr[noop_pos + 1], md->code.len - noop_pos - 1); md->code.len--;*/ /* eliminate the NOOP instruction */ eliminate_instructions (moo, noop_pos, noop_pos); goto done; } } while (1); done: return 0; } static int compile_basic_expression (moo_t* moo, const moo_oocs_t* ident, const moo_ioloc_t* ident_loc, int ident_dotted) { /* * basic-expression := expression-primary message-expression? */ moo_method_data_t* md = get_cunit_method_data(moo); moo_oow_pool_t jumptoend; moo_oow_pool_chunk_t* jumptoend_chunk; moo_ioloc_t expr_loc; moo_oow_t i, j; int to_super; expr_loc = *TOKEN_LOC(moo); init_oow_pool (moo, &jumptoend); start_over: if (compile_expression_primary(moo, ident, ident_loc, ident_dotted, &to_super) <= -1) goto oops; if (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT || TOKEN_TYPE(moo) == MOO_IOTOK_BINSEL || TOKEN_TYPE(moo) == MOO_IOTOK_KEYWORD) { if (compile_message_expression(moo, to_super) <= -1) goto oops; } if (TOKEN_TYPE(moo) == MOO_IOTOK_AND || TOKEN_TYPE(moo) == MOO_IOTOK_OR) { int bcode; bcode = (TOKEN_TYPE(moo) == MOO_IOTOK_AND)? BCODE_JUMP_FORWARD_IF_FALSE: BCODE_JUMP_FORWARD_IF_TRUE; /* TODO: optimization if the expression is a known constant that can be determined to be boolean */ if (add_to_oow_pool(moo, &jumptoend, md->code.len, TOKEN_LOC(moo)) <= -1 || emit_single_param_instruction(moo, bcode, MAX_CODE_JUMP, TOKEN_LOC(moo)) <= -1 || emit_byte_instruction(moo, BCODE_POP_STACKTOP, TOKEN_LOC(moo)) <= -1) goto oops; GET_TOKEN (moo); /* compile_method_expression() calls this function with a non-null * identifer if it encounters an assignment operator after an identifer. * i only allow a basic expression after a logical operator. * the basic expression doesn't include an assignment expression * if it is not in the parenthesis. so i nullify the following 2 variables * * a := 30 > 20 and 10 > 20 is equavelent to a := (30 > 20 and 10 > 20). * * you will encounter a syntax error for the following expression as the * expression end after y. * a := 30 > 20 and y := 10 > 20 * * a := 30 > 20 and (y := 10 > 20) is perfectly valid. */ ident = MOO_NULL; ident_loc = MOO_NULL; goto start_over; } /* patch instructions that jumps to the end of logical expression for short-circuited evaluation */ for (jumptoend_chunk = jumptoend.head, i = 0; jumptoend_chunk; jumptoend_chunk = jumptoend_chunk->next) { for (j = 0; j < MOO_COUNTOF(jumptoend.static_chunk.buf) && i < jumptoend.count; j++) { if (patch_forward_jump_instruction (moo, jumptoend_chunk->buf[j].v, md->code.len) <= -1) { /* the logical expression is too large to patch the jump instruction */ moo_setsynerrbfmt (moo, MOO_SYNERR_INSTFLOOD, &expr_loc, MOO_NULL, "unable to patch logical operator jump"); goto oops; } i++; } } fini_oow_pool (moo, &jumptoend); return 0; oops: fini_oow_pool (moo, &jumptoend); return -1; } static int compile_braced_block (moo_t* moo) { /* handle a code block enclosed in { } */ moo_method_data_t* md = get_cunit_method_data(moo); moo_oow_t code_start; /*TODO: support local variable declaration inside {} */ if (TOKEN_TYPE(moo) != MOO_IOTOK_LBRACE) { moo_setsynerr (moo, MOO_SYNERR_LBRACE, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); #if 0 code_start = md->code.len; if (TOKEN_TYPE(moo) != MOO_IOTOK_RBRACE) { moo_oow_t pop_stacktop_pos = 0; while (TOKEN_TYPE(moo) != MOO_IOTOK_EOF) { int n; n = compile_block_statement(moo); if (n <= -1) return -1; if (n == 8888) { if (TOKEN_TYPE(moo) == MOO_IOTOK_RBRACE) { /* non-statement followed by the closing brace. * if there is a statement above the non-statement item, the POP_STACKSTOP is produced. * it should be eliminated since the block should return the last evalulated value */ if (pop_stacktop_pos > 0) eliminate_instructions (moo, pop_stacktop_pos, pop_stacktop_pos); break; } } else if (n == 7777) { /* goto statement */ if (TOKEN_TYPE(moo) == MOO_IOTOK_RBRACE) { if (pop_stacktop_pos > 0) eliminate_instructions (moo, pop_stacktop_pos, pop_stacktop_pos); break; } else if (TOKEN_TYPE(moo) == MOO_IOTOK_PERIOD) { GET_TOKEN (moo); if (TOKEN_TYPE(moo) == MOO_IOTOK_RBRACE) break; } else { moo_setsynerr (moo, MOO_SYNERR_RBRACE, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } } else { if (TOKEN_TYPE(moo) == MOO_IOTOK_RBRACE) break; else if (TOKEN_TYPE(moo) == MOO_IOTOK_PERIOD) { moo_ioloc_t period_loc = *TOKEN_LOC(moo); GET_TOKEN (moo); if (TOKEN_TYPE(moo) == MOO_IOTOK_RBRACE) break; pop_stacktop_pos = md->code.len; /* remember the position of the last POP_STACKTOP for elimination */ if (emit_byte_instruction(moo, BCODE_POP_STACKTOP, &period_loc) <= -1) return -1; } else { moo_setsynerr (moo, MOO_SYNERR_RBRACE, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } } } } if (md->code.len == code_start) { /* the block doesn't contain an instruction at all */ if (emit_byte_instruction(moo, BCODE_PUSH_NIL, TOKEN_LOC(moo)) <= -1) return -1; } #else code_start = md->code.len; if (emit_byte_instruction(moo, BCODE_PUSH_NIL, TOKEN_LOC(moo)) <= -1) return -1; if (TOKEN_TYPE(moo) != MOO_IOTOK_RBRACE) { moo_oow_t pop_stacktop_pos; pop_stacktop_pos = md->code.len; if (emit_byte_instruction(moo, BCODE_POP_STACKTOP, TOKEN_LOC(moo)) <= -1) return -1; do { int n; if (TOKEN_TYPE(moo) == MOO_IOTOK_EOF) { moo_setsynerr (moo, MOO_SYNERR_RBRACE, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } n = compile_block_statement(moo); if (n <= -1) return -1; if (n == 8888) { /* compile_block_statement() processed non-statement item like a jump label. */ MOO_ASSERT (moo, md->_label != MOO_NULL); if (TOKEN_TYPE(moo) == MOO_IOTOK_RBRACE) { /* the last label inside {} must be followed by a valid statement */ moo_oocs_t labname; labname.ptr = (moo_ooch_t*)(md->_label + 1); labname.len = moo_count_oocstr(labname.ptr); moo_setsynerrbfmt (moo, MOO_SYNERR_LABELATEND, &md->_label->loc, &labname, "label at end of braced block"); return -1; } } else if (n == 7777) { /* goto statement */ if (TOKEN_TYPE(moo) == MOO_IOTOK_RBRACE) { /* jumping somewhere inside {} is different from inside []. * [] needs to leave a return value when leaving the block. * {} doesn't need to because {} can only be used as part of 'if', 'while', 'until', 'do'. * that means, the last evaluated value inside {} before exit is used * as a lvalue but the jump target can't be break into an assignment statement. * * a : 3. * a := if (1) { goto L30 }. // if jump to L30 is made inside {}, assignment to 'a' is skipped. * // so the evaluation result of the 'if' statement goes unused. * L30: b := a + 1. // therefore, 'a' holds 3. * * it is not allowed to place label inside "b := a + 1". * so the pop_stacktop produced must not be eliminated. */ break; } else if (TOKEN_TYPE(moo) == MOO_IOTOK_PERIOD) { GET_TOKEN (moo); if (TOKEN_TYPE(moo) == MOO_IOTOK_RBRACE) break; } else { moo_setsynerr (moo, MOO_SYNERR_RBRACE, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } } else { pop_stacktop_pos = md->code.len; /* remember the position of the last POP_STACKTOP for elimination */ if (emit_byte_instruction(moo, BCODE_POP_STACKTOP, TOKEN_LOC(moo)) <= -1) return -1; if (TOKEN_TYPE(moo) == MOO_IOTOK_RBRACE) { eliminate_instructions (moo, pop_stacktop_pos, pop_stacktop_pos); pop_stacktop_pos = INVALID_IP; break; } else if (TOKEN_TYPE(moo) == MOO_IOTOK_PERIOD) { GET_TOKEN (moo); if (TOKEN_TYPE(moo) == MOO_IOTOK_RBRACE) { eliminate_instructions(moo, pop_stacktop_pos, pop_stacktop_pos); pop_stacktop_pos = INVALID_IP; break; } } else { moo_setsynerr (moo, MOO_SYNERR_RBRACE, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } } } while (1); MOO_ASSERT (moo, md->code.len > code_start); if (md->code.len - code_start >= 2 && md->code.ptr[code_start] == BCODE_PUSH_NIL && md->code.ptr[code_start + 1] == BCODE_POP_STACKTOP) { /* elminnate the block prologue */ eliminate_instructions(moo, code_start, code_start + 1); } } #endif if (TOKEN_TYPE(moo) != MOO_IOTOK_RBRACE) { moo_setsynerr (moo, MOO_SYNERR_RBRACE, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } return 0; } static int compile_conditional (moo_t* moo) { if (TOKEN_TYPE(moo) != MOO_IOTOK_LPAREN) { moo_setsynerr (moo, MOO_SYNERR_LPAREN, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); /* a weird expression like this is also allowed for the call to compile_method_expression() * if (if (a == 10) { ^20 }) { ^40 }. */ if (compile_method_expression(moo, 0) <= -1) return -1; if (TOKEN_TYPE(moo) != MOO_IOTOK_RPAREN) { moo_setsynerr (moo, MOO_SYNERR_LPAREN, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } return 0; } static int compile_if_expression (moo_t* moo) { moo_method_data_t* md = get_cunit_method_data(moo); moo_oow_pool_t jumptoend; moo_oow_pool_chunk_t* jumptoend_chunk; moo_oow_t i, j; moo_oow_t jumptonext, precondpos, postcondpos, endoftrueblock; moo_ioloc_t if_loc, brace_loc; int jmpop_inst, push_true_inst, push_false_inst; moo_label_t* la, * lb; MOO_ASSERT (moo, TOKEN_TYPE(moo) == MOO_IOTOK_IF || TOKEN_TYPE(moo) == MOO_IOTOK_IFNOT); if_loc = *TOKEN_LOC(moo); init_oow_pool (moo, &jumptoend); jumptonext = INVALID_IP; endoftrueblock = INVALID_IP; if (TOKEN_TYPE(moo) == MOO_IOTOK_IF) { /* if */ push_true_inst = BCODE_PUSH_TRUE; push_false_inst = BCODE_PUSH_FALSE; jmpop_inst = BCODE_JMPOP_FORWARD_IF_FALSE; } else { /* ifnot */ push_true_inst = BCODE_PUSH_FALSE; push_false_inst = BCODE_PUSH_TRUE; jmpop_inst = BCODE_JMPOP_FORWARD_IF_TRUE; } do { GET_TOKEN (moo); /* get ( */ precondpos = md->code.len; if (jumptonext != INVALID_IP && patch_forward_jump_instruction(moo, jumptonext, precondpos) <= -1) { moo_setsynerrbfmt (moo, MOO_SYNERR_INSTFLOOD, &brace_loc, MOO_NULL, "unable to patch conditional branching jump"); goto oops; } if (compile_conditional(moo) <= -1) goto oops; postcondpos = md->code.len; /* remember position of the jmpop_forward_if_false instruction to be generated */ jumptonext = md->code.len; /* BCODE_JMPOP_FORWARD_IF_FALSE is always a long jump instruction. * just specify MAX_CODE_JUMP for consistency with short jump variants */ if (emit_single_param_instruction(moo, jmpop_inst, MAX_CODE_JUMP, &if_loc) <= -1) goto oops; GET_TOKEN (moo); /* get { */ brace_loc = *TOKEN_LOC(moo); la = md->_label; if (compile_braced_block(moo) <= -1) goto oops; lb = md->_label; /* [NOTE] * it checks by comparing 'la' and 'b' if there has been a label found inside a braced block. * the code below doesn't eliminate instructions emitted of a braced block containing one or * more labels. * * the check is suboptimal and primitive. an optimizing compiler needs to check if the actual * jump is to be made made into the blcok. TODO: optimize it further */ if (la == lb && precondpos + 1 == postcondpos) { if (md->code.ptr[precondpos] == push_true_inst) { /* got 'if (true)' or 'ifnot (false)' */ eliminate_instructions (moo, jumptonext, jumptonext + MOO_BCODE_LONG_PARAM_SIZE); jumptonext = INVALID_IP; /* eliminate PUSH_TRUE */ eliminate_instructions (moo, precondpos, precondpos); postcondpos = precondpos; if (endoftrueblock == INVALID_IP) { /* update the end position of the first true block */ endoftrueblock = md->code.len; } } else if (md->code.ptr[precondpos] == push_false_inst) { /* got 'if (false)' or 'ifnot (true)' */ eliminate_instructions (moo, jumptonext, jumptonext + MOO_BCODE_LONG_PARAM_SIZE); jumptonext = INVALID_IP; /* the conditional was false. eliminate instructions emitted * for the block attached to the conditional */ eliminate_instructions (moo, precondpos, md->code.len - 1); postcondpos = precondpos; } else goto normal_cond; } else { normal_cond: if (endoftrueblock == INVALID_IP) { /* emit an instruction to jump to the end */ if (add_to_oow_pool(moo, &jumptoend, md->code.len, TOKEN_LOC(moo)) <= -1 || emit_single_param_instruction(moo, BCODE_JUMP_FORWARD, MAX_CODE_JUMP, TOKEN_LOC(moo)) <= -1) goto oops; } } GET_TOKEN (moo); /* get the next token after } */ if (TOKEN_TYPE(moo) != MOO_IOTOK_ELIF && TOKEN_TYPE(moo) != MOO_IOTOK_ELIFNOT) break; if (TOKEN_TYPE(moo) == MOO_IOTOK_ELIF) { push_true_inst = BCODE_PUSH_TRUE; push_false_inst = BCODE_PUSH_FALSE; jmpop_inst = BCODE_JMPOP_FORWARD_IF_FALSE; } else { push_true_inst = BCODE_PUSH_FALSE; push_false_inst = BCODE_PUSH_TRUE; jmpop_inst = BCODE_JMPOP_FORWARD_IF_TRUE; } } while (1); if (jumptonext != INVALID_IP && patch_forward_jump_instruction(moo, jumptonext, md->code.len) <= -1) { moo_setsynerrbfmt (moo, MOO_SYNERR_INSTFLOOD, &brace_loc, MOO_NULL, "unable to patch conditional branching jump"); goto oops; } la = md->_label; if (TOKEN_TYPE(moo) == MOO_IOTOK_ELSE) { GET_TOKEN (moo); /* get { */ if (compile_braced_block(moo) <= -1) goto oops; GET_TOKEN (moo); /* get the next token after } */ } else { /* emit an instruction to push nil if no 'else' part exists */ if (emit_byte_instruction(moo, BCODE_PUSH_NIL, TOKEN_LOC(moo)) <= -1) goto oops; } lb = md->_label; if (la == lb && endoftrueblock != INVALID_IP) { /* eliminate all instructions after the end of the first true block found */ eliminate_instructions (moo, endoftrueblock, md->code.len - 1); } /* patch instructions that jumps to the end of if expression */ for (jumptoend_chunk = jumptoend.head, i = 0; jumptoend_chunk; jumptoend_chunk = jumptoend_chunk->next) { for (j = 0; j < MOO_COUNTOF(jumptoend.static_chunk.buf) && i < jumptoend.count; j++) { if (patch_forward_jump_instruction(moo, jumptoend_chunk->buf[j].v, md->code.len) <= -1) { moo_setsynerrbfmt (moo, MOO_SYNERR_INSTFLOOD, &jumptoend_chunk->buf[j].loc, MOO_NULL, "unable to patch conditional branching jump"); goto oops; } i++; } } fini_oow_pool (moo, &jumptoend); return 0; oops: fini_oow_pool (moo, &jumptoend); return -1; } static int compile_while_expression (moo_t* moo) /* or compile_until_expression */ { moo_method_data_t* md = get_cunit_method_data(moo); moo_ioloc_t while_loc, brace_loc, closing_brace_loc; moo_oow_t precondpos, postcondpos, prebbpos, postbbpos; int cond_style = 0, loop_pushed = 0, is_until_loop; moo_label_t* la, * lb; MOO_ASSERT (moo, TOKEN_TYPE(moo) == MOO_IOTOK_WHILE || TOKEN_TYPE(moo) == MOO_IOTOK_UNTIL); is_until_loop = (TOKEN_TYPE(moo) == MOO_IOTOK_UNTIL); while_loc = *TOKEN_LOC(moo); GET_TOKEN (moo); /* get (, verification is done inside compile_conditional() */ precondpos = md->code.len; if (compile_conditional(moo) <= -1) goto oops; postcondpos = md->code.len; if (precondpos + 1 == postcondpos) { moo_uint8_t inst1, inst2; if (is_until_loop) { inst1 = BCODE_PUSH_FALSE; inst2 = BCODE_PUSH_TRUE; } else { inst1 = BCODE_PUSH_TRUE; inst2 = BCODE_PUSH_FALSE; } /* simple optimization - * if the conditional is known to be true, emit the absolute jump instruction. * if it is known to be false, kill all generated instructions. */ if (md->code.ptr[precondpos] == inst1) { /* the conditional is always true for while, or false for until*/ cond_style = 1; eliminate_instructions (moo, precondpos, md->code.len - 1); postcondpos = precondpos; } else if (md->code.ptr[precondpos] == inst2) { /* the conditional is always false for while, or false for until */ cond_style = -1; } /* TODO: at least check some other literals for optimization. * most literal values must be evaluate to true. */ } if (cond_style != 1) { /* BCODE_JMPOP_FORWARD_IF_FALSE is always a long jump instruction. * just specify MAX_CODE_JUMP for consistency with short jump variants */ if (emit_single_param_instruction(moo, (is_until_loop? BCODE_JMPOP_FORWARD_IF_TRUE: BCODE_JMPOP_FORWARD_IF_FALSE), MAX_CODE_JUMP, TOKEN_LOC(moo)) <= -1) goto oops; } /* remember information about this while loop. */ if (push_loop(moo, MOO_LOOP_WHILE, precondpos) <= -1) goto oops; loop_pushed = 1; GET_TOKEN (moo); /* get { */ brace_loc = *TOKEN_LOC(moo); prebbpos = md->code.len; la = md->_label; if (compile_braced_block(moo) <= -1) goto oops; lb = md->_label; closing_brace_loc = *TOKEN_LOC(moo); GET_TOKEN (moo); /* get the next token after } */ postbbpos = md->code.len; if (la == lb && prebbpos + 1 == postbbpos && md->code.ptr[prebbpos] == BCODE_PUSH_NIL) { /* optimization - * the braced block is kind of empty as it only pushes nil. * get rid of this push instruction and don't generate the POP_STACKTOP */ eliminate_instructions (moo, prebbpos, md->code.len - 1); } else if (prebbpos < postbbpos) { /* emit an instruction to pop the value pushed by the braced block */ if (emit_byte_instruction(moo, BCODE_POP_STACKTOP, &closing_brace_loc) <= -1) goto oops; } /* emit an instruction to jump back to the condition */ if (emit_backward_jump_instruction(moo, BCODE_JUMP_BACKWARD, md->code.len - precondpos, &closing_brace_loc) <= -1) { if (moo->errnum == MOO_ERANGE) { /* the jump offset is out of the representable range by the offset * portion of the jump instruction */ moo_setsynerr (moo, MOO_SYNERR_INSTFLOOD, &while_loc, MOO_NULL); } goto oops; } if (cond_style != 1) { if (patch_forward_jump_instruction(moo, postcondpos, md->code.len) <= -1) { moo_setsynerrbfmt (moo, MOO_SYNERR_INSTFLOOD, &brace_loc, MOO_NULL, "unable to patch conditional loop jump"); goto oops; } } if (la == lb && cond_style == -1) { /* optimization - get rid of instructions generated for the while * loop including the conditional as the condition was false */ eliminate_instructions (moo, precondpos, md->code.len - 1); } /* patch the jump instructions for break */ if (update_loop_breaks(moo, md->code.len) <= -1) goto oops; /* destroy the loop information stored earlier in this function */ pop_loop (moo); loop_pushed = 0; /* push nil as a result of the while expression. TODO: is it the best value? anything else? */ if (emit_byte_instruction(moo, BCODE_PUSH_NIL, &closing_brace_loc) <= -1) goto oops; return 0; oops: if (loop_pushed) pop_loop (moo); return -1; } static int compile_do_while_expression (moo_t* moo) { moo_method_data_t* md = get_cunit_method_data(moo); moo_ioloc_t do_loc, closing_brace_loc; moo_oow_t precondpos, postcondpos, prebbpos, postbbpos; int jbinst = 0, loop_pushed = 0, is_until_loop; moo_loop_t* loop = MOO_NULL; moo_label_t* la, * lb; MOO_ASSERT (moo, TOKEN_TYPE(moo) == MOO_IOTOK_DO); do_loc = *TOKEN_LOC(moo); GET_TOKEN (moo); /* get { */ prebbpos = md->code.len; /* remember information about this loop. * position of the conditional is not known yet.*/ if (push_loop(moo, MOO_LOOP_DO_WHILE, prebbpos) <= -1) goto oops; loop_pushed = 1; la = md->_label; if (compile_braced_block(moo) <= -1) goto oops; lb = md->_label; closing_brace_loc = *TOKEN_LOC(moo); GET_TOKEN (moo); /* get the next token after } */ if (TOKEN_TYPE(moo) == MOO_IOTOK_UNTIL) is_until_loop = 1; else if (TOKEN_TYPE(moo) == MOO_IOTOK_WHILE) is_until_loop = 0; else { moo_setsynerr (moo, MOO_SYNERR_WHILE, TOKEN_LOC(moo), TOKEN_NAME(moo)); goto oops; } GET_TOKEN (moo); /* get ( */ postbbpos = md->code.len; if (la == lb && prebbpos + 1 == postbbpos && md->code.ptr[prebbpos] == BCODE_PUSH_NIL) { /* optimization - * the braced block is kind of empty as it only pushes nil. * get rid of this push instruction and don't generate the POP_STACKTOP */ eliminate_instructions (moo, prebbpos, md->code.len - 1); precondpos = prebbpos; } else if (prebbpos < postbbpos) { /* emit code to pop the value pushed by the braced block */ if (emit_byte_instruction(moo, BCODE_POP_STACKTOP, &closing_brace_loc) <= -1) goto oops; } precondpos = md->code.len; /* update jump instructions emitted for continue */ if (update_loop_continues (moo, precondpos) <= -1) goto oops; /* cannnot destroy the loop information because of pending jump updates * for break. but need to unlink it as the conditional is not really * part of the loop body */ loop = unlink_loop (moo); if (compile_conditional (moo) <= -1) goto oops; postcondpos = md->code.len; jbinst = (is_until_loop? BCODE_JMPOP_BACKWARD_IF_FALSE: BCODE_JMPOP_BACKWARD_IF_TRUE); if (precondpos + 1 == postcondpos) { /* simple optimization - * if the conditional is known to be true, emit the absolute jump instruction. * if it is known to be false, kill all generated instructions. */ if (md->code.ptr[precondpos] == (is_until_loop? BCODE_PUSH_FALSE: BCODE_PUSH_TRUE)) { /* the conditional is always true. eliminate PUSH_TRUE and emit an absolute jump */ eliminate_instructions (moo, precondpos, precondpos); postcondpos = precondpos; jbinst = BCODE_JUMP_BACKWARD; } else if (md->code.ptr[precondpos] == (is_until_loop? BCODE_PUSH_TRUE: BCODE_PUSH_FALSE)) { /* the conditional is always false. eliminate PUSH_FALSE and don't emit jump */ eliminate_instructions (moo, precondpos, precondpos); postcondpos = precondpos; goto skip_emitting_jump_backward; } } if (emit_backward_jump_instruction(moo, jbinst, md->code.len - prebbpos, TOKEN_LOC(moo)) <= -1) { if (moo->errnum == MOO_ERANGE) { /* the jump offset is out of the representable range by the offset * portion of the jump instruction */ moo_setsynerr (moo, MOO_SYNERR_INSTFLOOD, &do_loc, MOO_NULL); } goto oops; } skip_emitting_jump_backward: GET_TOKEN (moo); /* get the next token after ) */ /* update jump instructions emitted for break */ if (update_loop_jumps(moo, &loop->break_ip_pool, md->code.len) <= -1) return -1; free_loop (moo, loop); /* destroy the unlinked loop information */ loop = MOO_NULL; loop_pushed = 0; /* push nil as a result of the while expression. TODO: is it the best value? anything else? */ if (emit_byte_instruction(moo, BCODE_PUSH_NIL, TOKEN_LOC(moo)) <= -1) goto oops; return 0; oops: if (loop_pushed) { if (loop) free_loop (moo, loop); else pop_loop (moo); } return -1; } static int compile_method_expression (moo_t* moo, int pop) { /* * method-expression := method-assignment-expression | basic-expression | if-expression | while-expression | do-while-expression * method-assignment-expression := identifier ":=" method-expression * if-expression := if ( ) { } elif { } else { } * while-expression := while () {} * do-while-expression := do { } while () */ moo_method_data_t* md = get_cunit_method_data(moo); moo_oocs_t assignee; moo_oow_t index; int ret = 0; MOO_ASSERT (moo, pop == 0 || pop == 1); MOO_MEMSET (&assignee, 0, MOO_SIZEOF(assignee)); if (TOKEN_TYPE(moo) == MOO_IOTOK_IF || TOKEN_TYPE(moo) == MOO_IOTOK_IFNOT) { if (compile_if_expression(moo) <= -1) return -1; } else if (TOKEN_TYPE(moo) == MOO_IOTOK_WHILE || TOKEN_TYPE(moo) == MOO_IOTOK_UNTIL) { if (compile_while_expression(moo) <= -1) return -1; } else if (TOKEN_TYPE(moo) == MOO_IOTOK_DO) { if (compile_do_while_expression(moo) <= -1) return -1; } else if (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT || TOKEN_TYPE(moo) == MOO_IOTOK_IDENT_DOTTED) { moo_ioloc_t assignee_loc; moo_oow_t assignee_offset; int assignee_dotted; /* store the assignee name to the internal buffer * to make it valid after the token buffer has been overwritten */ assignee = *TOKEN_NAME(moo); if (clone_assignee(moo, &assignee, &assignee_offset) <= -1) return -1; assignee_loc = moo->c->tok.loc; assignee_dotted = (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT_DOTTED); GET_TOKEN (moo); if (TOKEN_TYPE(moo) == MOO_IOTOK_ASSIGN) { /* assignment expression */ var_info_t var; moo_ioloc_t assop_loc; assop_loc = *TOKEN_LOC(moo); GET_TOKEN (moo); if (compile_method_expression(moo, 0) <= -1) goto oops; /* compile_method_expression() is called after clone_assignee(). * clone_assignee() may reallocate a single buffer that holds * a series of assigness names. this pointer based operation is * fragile as it can change. use the offset of the cloned * assignee to update the actual pointer after the recursive * compile_method_expression() call */ assignee.ptr = &md->assignees.ptr[assignee_offset]; if (get_variable_info(moo, &assignee, &assignee_loc, assignee_dotted, &var) <= -1) goto oops; switch (var.type) { case VAR_ARGUMENT: /* TODO: consider if assigning to an argument should be disallowed */ case VAR_TEMPORARY: { #if defined(MOO_USE_CTXTEMPVAR) if (md->blk_depth > 0) { moo_oow_t i; /* if a temporary variable is accessed inside a block, * use a special instruction to indicate it */ MOO_ASSERT (moo, var.pos < md->blk_tmprcnt[md->blk_depth]); for (i = md->blk_depth; i > 0; i--) { if (var.pos >= md->blk_tmprcnt[i - 1]) { if (emit_double_param_instruction(moo, (pop? BCODE_POP_INTO_CTXTEMPVAR_0: BCODE_STORE_INTO_CTXTEMPVAR_0), md->blk_depth - i, var.pos - md->blk_tmprcnt[i - 1], &assop_loc) <= -1) return -1; goto temporary_done; } } } #endif if (emit_single_param_instruction(moo, (pop? BCODE_POP_INTO_TEMPVAR_0: BCODE_STORE_INTO_TEMPVAR_0), var.pos, &assop_loc) <= -1) goto oops; temporary_done: ret = pop; break; } case VAR_INSTANCE: case VAR_CLASSINST: if (emit_single_param_instruction(moo, (pop? BCODE_POP_INTO_INSTVAR_0: BCODE_STORE_INTO_INSTVAR_0), var.pos, &assop_loc) <= -1) goto oops; ret = pop; break; case VAR_CLASS: if (add_literal(moo, (moo_oop_t)var.cls, &index) <= -1 || emit_double_param_instruction(moo, (pop? BCODE_POP_INTO_OBJVAR_0: BCODE_STORE_INTO_OBJVAR_0), var.pos, index, &assop_loc) <= -1) goto oops; ret = pop; break; case VAR_GLOBAL: if (add_literal(moo, (moo_oop_t)var.u.gbl, &index) <= -1 || emit_single_param_instruction(moo, (pop? BCODE_POP_INTO_OBJECT_0: BCODE_STORE_INTO_OBJECT_0), index, &assop_loc) <= -1) return -1; ret = pop; break; default: moo_seterrnum (moo, MOO_EINTERN); goto oops; } } else { /* what is held in assignee is not an assignee any more. * potentially it is a variable or object reference * to be pushed on to the stack */ assignee.ptr = &md->assignees.ptr[assignee_offset]; if (compile_basic_expression(moo, &assignee, &assignee_loc, assignee_dotted) <= -1) goto oops; } } else { assignee.len = 0; if (compile_basic_expression(moo, MOO_NULL, MOO_NULL, 0) <= -1) goto oops; } md->assignees.len -= assignee.len; return ret; oops: md->assignees.len -= assignee.len; return -1; } static MOO_INLINE int resolve_goto_label (moo_t* moo, moo_goto_t* _goto) { moo_method_data_t* md = get_cunit_method_data(moo); moo_oocs_t gtname; moo_label_t* _label; gtname.ptr = (moo_ooch_t*)(_goto + 1); _label = md->_label; while (_label) { const moo_ooch_t* lbname; lbname = (const moo_ooch_t*)(_label + 1); if (moo_comp_oocstr(gtname.ptr, lbname) == 0) { if (_goto->blk_id != _label->blk_id || _goto->blk_depth != _label->blk_depth) { gtname.len = moo_count_oocstr(gtname.ptr); moo_setsynerrbfmt (moo, MOO_SYNERR_NAMEUNDEF, &_goto->loc, >name, "goto disallowed to different block level"); return -1; } MOO_ASSERT (moo, _goto->ip != INVALID_IP); /*MOO_ASSERT (moo, _goto->ip != _label->ip); in 'label: goto label', _goto->ip and _label->ip are the same. */ if (patch_forward_jump_instruction(moo, _goto->ip, _label->ip) <= -1) { moo_setsynerrbfmt (moo, MOO_SYNERR_INSTFLOOD, &_goto->loc, MOO_NULL, "unable to patch unconditional jump"); return -1; } return 0; } _label = _label->next; } gtname.len = moo_count_oocstr(gtname.ptr); moo_setsynerrbfmt (moo, MOO_SYNERR_NAMEUNDEF, &_goto->loc, >name, "undefined goto label"); return -1; } static MOO_INLINE int resolve_goto_labels (moo_t* moo) { moo_method_data_t* md = get_cunit_method_data(moo); moo_goto_t* _goto; _goto = md->_goto; while (_goto) { if (_goto->ip != INVALID_IP && resolve_goto_label(moo, _goto) <= -1) return -1; _goto = _goto->next; } return 0; } static MOO_INLINE int add_label (moo_t* moo, const moo_oocs_t* name, const moo_ioloc_t* lab_loc, moo_oow_t blkid, moo_oow_t blkdepth, moo_oow_t ip) { moo_method_data_t* md = get_cunit_method_data(moo); moo_label_t* lab; moo_ooch_t* nptr; moo_oocs_t lab_name; MOO_ASSERT (moo, name->len > 0); lab_name = *name; if (lab_name.ptr[lab_name.len - 1] == ':') lab_name.len--; /* if the name ends with a trailing colon */ lab = md->_label; while (lab) { nptr = (moo_ooch_t*)(lab + 1); if (moo_comp_oochars_oocstr(lab_name.ptr, lab_name.len, nptr) == 0) { /* duplicate label name */ moo_setsynerrbfmt (moo, MOO_SYNERR_NAMEDUPL, lab_loc, &lab_name, "duplicate label name"); return -1; } lab = lab->next; } lab = (moo_label_t*)moo_allocmem(moo, MOO_SIZEOF(*lab) + (lab_name.len + 1) * MOO_SIZEOF(moo_ooch_t)); if (!lab) return -1; nptr = (moo_ooch_t*)(lab + 1); moo_copy_oochars (nptr, lab_name.ptr, lab_name.len); nptr[lab_name.len] = '\0'; lab->blk_id = blkid; lab->blk_depth = blkdepth; lab->ip = ip; lab->loc = *lab_loc; lab->next = md->_label; md->_label = lab; return 0; } static int compile_goto_statement (moo_t* moo) { moo_method_data_t* md = get_cunit_method_data(moo); moo_goto_t* _goto; moo_oocs_t* target; moo_ooch_t* nptr; if (TOKEN_TYPE(moo) != MOO_IOTOK_IDENT) { moo_setsynerr (moo, MOO_SYNERR_GOTOTARGETINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } target = TOKEN_NAME(moo); _goto = (moo_goto_t*)moo_allocmem(moo, MOO_SIZEOF(*_goto) + (target->len + 1) * MOO_SIZEOF(moo_ooch_t)); if (!_goto) return -1; nptr = (moo_ooch_t*)(_goto + 1); moo_copy_oochars (nptr, target->ptr, target->len); nptr[target->len] = '\0'; _goto->ip = md->code.len; if (emit_single_param_instruction(moo, BCODE_JUMP_FORWARD, MAX_CODE_JUMP, TOKEN_LOC(moo)) <= -1) { moo_freemem (moo, _goto); return -1; } _goto->blk_id = md->blk_id; _goto->blk_depth = md->blk_depth; _goto->loc = *TOKEN_LOC(moo); _goto->next = md->_goto; md->_goto = _goto; GET_TOKEN (moo); /* read the next token to the target label */ return 0; } static int compile_special_statement (moo_t* moo) { moo_method_data_t* md = get_cunit_method_data(moo); moo_ioloc_t start_loc = *TOKEN_LOC(moo); if (TOKEN_TYPE(moo) == MOO_IOTOK_RETURN) { /* ^ - return - return to the sender of the origin */ GET_TOKEN (moo); if (compile_method_expression(moo, 0) <= -1) return -1; return emit_byte_instruction(moo, BCODE_RETURN_STACKTOP, &start_loc); } else if (TOKEN_TYPE(moo) == MOO_IOTOK_LOCAL_RETURN) { /* ^^ - local return - return to the origin */ GET_TOKEN (moo); if (compile_method_expression(moo, 0) <= -1) return -1; return emit_byte_instruction(moo, BCODE_LOCAL_RETURN, &start_loc); } else if (TOKEN_TYPE(moo) == MOO_IOTOK_BREAK) { if (!md->loop) { /* break outside a loop */ moo_setsynerr (moo, MOO_SYNERR_NOTINLOOP, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (md->loop->blkcount > 0) { /* break cannot cross boundary of a block */ moo_setsynerr (moo, MOO_SYNERR_INBLOCK, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); /* read the next token to break */ return inject_break_to_loop(moo, &start_loc); } else if (TOKEN_TYPE(moo) == MOO_IOTOK_CONTINUE) { if (!md->loop) { moo_setsynerr (moo, MOO_SYNERR_NOTINLOOP, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (md->loop->blkcount > 0) { /* continue cannot cross boundary of a block */ moo_setsynerr (moo, MOO_SYNERR_INBLOCK, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); /* read the next token to continue */ return (md->loop->type == MOO_LOOP_DO_WHILE)? inject_continue_to_loop(moo, &start_loc): /* in a do-while loop, the position to the conditional is not known yet */ emit_backward_jump_instruction(moo, BCODE_JUMP_BACKWARD, md->code.len - md->loop->startpos, &start_loc); } else if (TOKEN_TYPE(moo) == MOO_IOTOK_KEYWORD) { /* this is a label */ /* remember the label location with the block depth */ if (add_label(moo, TOKEN_NAME(moo), TOKEN_LOC(moo), md->blk_id, md->blk_depth, md->code.len) <= -1) return -1; GET_TOKEN (moo); return 8888; /* indicates that non-statement has been seen and processed.*/ } else if (TOKEN_TYPE(moo) == MOO_IOTOK_GOTO) { GET_TOKEN (moo); if (compile_goto_statement(moo) <= -1) return -1; return 7777; /* indicate that a goto statement has been seen and processed */ } return 9999; /* to indicate that no special statement has been seen and processed */ } static int compile_block_statement (moo_t* moo) { /* compile_block_statement() is a simpler version of * of compile_method_statement(). it doesn't care to * produce the instruction to pop the stack top by passing * 0 as the second argument to compile_method_expression(). */ int n; n = compile_special_statement(moo); if (n <= -1) return -1; if (n == 9999) n = compile_method_expression(moo, 0); return n; } static int compile_method_statement (moo_t* moo) { /* * method-statement := method-return-statement | break | continue | method-expression * method-return-statement := "^" method-expression */ moo_method_data_t* md = get_cunit_method_data(moo); int n; n = compile_special_statement(moo); if (n <= -1) return -1; if (n == 9999) { /* the second parameter to compile_method_expression() indicates * that the stack top will eventually be popped off. the compiler * can optimize some instruction sequencese. for example, two * consecutive store and pop intructions can be transformed to * a more specialized single pop-and-store instruction. * the compile_method_expression() function emits POP_INTO_XXX * instructions if the second parameter is 1 whenever possible and * STORE_INTO_XXX if it's 0.*/ moo_oow_t preexprpos; preexprpos = md->code.len; n = compile_method_expression(moo, 1); if (n <= -1) return -1; /* if n is 1, no stack popping is required as POP_INTO_XXX has been * emitted in place of STORE_INTO_XXX. */ if (n == 0) { if (preexprpos + 1 == md->code.len) { /* TODO: MORE optimization. if expresssion is a literal, no push and pop are required. check for multie-byte instructions as well */ switch (md->code.ptr[preexprpos]) { case BCODE_PUSH_NIL: case BCODE_PUSH_TRUE: case BCODE_PUSH_FALSE: case BCODE_PUSH_CONTEXT: case BCODE_PUSH_PROCESS: case BCODE_PUSH_NEGONE: case BCODE_PUSH_ZERO: case BCODE_PUSH_ONE: case BCODE_PUSH_TWO: /* eliminate the unneeded push instruction */ n = 0; eliminate_instructions (moo, preexprpos, md->code.len - 1); break; default: goto pop_stacktop; } } else { pop_stacktop: return emit_byte_instruction (moo, BCODE_POP_STACKTOP, TOKEN_LOC(moo)); } } } return n; } static int compile_method_statements (moo_t* moo) { /* * method-statements := method-statement ("." | ("." method-statements))* */ if (TOKEN_TYPE(moo) != MOO_IOTOK_EOF && TOKEN_TYPE(moo) != MOO_IOTOK_RBRACE) { do { int n; n = compile_method_statement(moo); if (n <= -1) return -1; if (n == 8888) { /* a non-statement such as label has been processed */ if (TOKEN_TYPE(moo) == MOO_IOTOK_EOF || TOKEN_TYPE(moo) == MOO_IOTOK_RBRACE) break; } else { /* a proper statement or a goto statement(if n == 7777) has been processed */ if (TOKEN_TYPE(moo) == MOO_IOTOK_PERIOD) { /* period after a statement */ GET_TOKEN (moo); if (TOKEN_TYPE(moo) == MOO_IOTOK_EOF || TOKEN_TYPE(moo) == MOO_IOTOK_RBRACE) break; } else { if (TOKEN_TYPE(moo) == MOO_IOTOK_EOF || TOKEN_TYPE(moo) == MOO_IOTOK_RBRACE) break; /* not a period, EOF, nor } */ moo_setsynerr (moo, MOO_SYNERR_PERIOD, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } } } while (1); } /* arrange to return the receiver if execution reached * the end of the method without explicit return */ return emit_byte_instruction(moo, BCODE_RETURN_RECEIVER, TOKEN_LOC(moo)); } static moo_ooi_t compute_preamble (moo_t* moo, moo_method_data_t* md) { moo_ooi_t preamble_code, preamble_index, preamble_flags; preamble_code = MOO_METHOD_PREAMBLE_NONE; preamble_index = 0; preamble_flags = 0; if (md->pftype <= 0) { /* no primitive is set - perform some mutation for simplicity and efficiency */ if (md->code.len <= 0) { preamble_code = MOO_METHOD_PREAMBLE_RETURN_RECEIVER; } else { if (md->code.ptr[0] == BCODE_RETURN_RECEIVER) { preamble_code = MOO_METHOD_PREAMBLE_RETURN_RECEIVER; } else if (md->code.len > 1 && md->code.ptr[1] == BCODE_RETURN_STACKTOP) { switch (md->code.ptr[0]) { case BCODE_PUSH_RECEIVER: preamble_code = MOO_METHOD_PREAMBLE_RETURN_RECEIVER; break; case BCODE_PUSH_CONTEXT: preamble_code = MOO_METHOD_PREAMBLE_RETURN_CONTEXT; break; case BCODE_PUSH_PROCESS: preamble_code = MOO_METHOD_PREAMBLE_RETURN_PROCESS; break; case BCODE_PUSH_RECEIVER_NS: preamble_code = MOO_METHOD_PREAMBLE_RETURN_RECEIVER_NS; break; case BCODE_PUSH_NIL: preamble_code = MOO_METHOD_PREAMBLE_RETURN_NIL; break; case BCODE_PUSH_TRUE: preamble_code = MOO_METHOD_PREAMBLE_RETURN_TRUE; break; case BCODE_PUSH_FALSE: preamble_code = MOO_METHOD_PREAMBLE_RETURN_FALSE; break; case BCODE_PUSH_NEGONE: preamble_code = MOO_METHOD_PREAMBLE_RETURN_NEGINDEX; preamble_index = 1; break; case BCODE_PUSH_ZERO: preamble_code = MOO_METHOD_PREAMBLE_RETURN_INDEX; preamble_index = 0; break; case BCODE_PUSH_ONE: preamble_code = MOO_METHOD_PREAMBLE_RETURN_INDEX; preamble_index = 1; break; case BCODE_PUSH_TWO: preamble_code = MOO_METHOD_PREAMBLE_RETURN_INDEX; preamble_index = 2; break; case BCODE_PUSH_INSTVAR_0: case BCODE_PUSH_INSTVAR_1: case BCODE_PUSH_INSTVAR_2: case BCODE_PUSH_INSTVAR_3: case BCODE_PUSH_INSTVAR_4: case BCODE_PUSH_INSTVAR_5: case BCODE_PUSH_INSTVAR_6: case BCODE_PUSH_INSTVAR_7: preamble_code = MOO_METHOD_PREAMBLE_RETURN_INSTVAR; preamble_index = md->code.ptr[0] & 0x7; /* low 3 bits */ break; } } else if (md->code.len > MOO_BCODE_LONG_PARAM_SIZE + 1 && md->code.ptr[MOO_BCODE_LONG_PARAM_SIZE + 1] == BCODE_RETURN_STACKTOP) { int i; switch (md->code.ptr[0]) { case BCODE_PUSH_INSTVAR_X: preamble_code = MOO_METHOD_PREAMBLE_RETURN_INSTVAR; goto set_preamble_index; case BCODE_PUSH_INTLIT: preamble_code = MOO_METHOD_PREAMBLE_RETURN_INDEX; goto set_preamble_index; case BCODE_PUSH_NEGINTLIT: preamble_code = MOO_METHOD_PREAMBLE_RETURN_NEGINDEX; goto set_preamble_index; set_preamble_index: preamble_index = 0; for (i = 1; i <= MOO_BCODE_LONG_PARAM_SIZE; i++) { preamble_index = (preamble_index << 8) | md->code.ptr[i]; } if (!MOO_OOI_IN_METHOD_PREAMBLE_INDEX_RANGE(preamble_index)) { /* the index got out of the range */ preamble_code = MOO_METHOD_PREAMBLE_NONE; preamble_index = 0; } } } } } else if (md->pftype == PFTYPE_NUMBERED) { preamble_code = MOO_METHOD_PREAMBLE_PRIMITIVE; preamble_index = md->pfnum; } else if (md->pftype == PFTYPE_NAMED) { preamble_code = MOO_METHOD_PREAMBLE_NAMED_PRIMITIVE; preamble_index = md->pfnum; /* index to literal frame */ } else if (md->pftype == PFTYPE_EXCEPTION) { preamble_code = MOO_METHOD_PREAMBLE_EXCEPTION; preamble_index = 0; } else { MOO_ASSERT (moo, md->pftype == PFTYPE_ENSURE); preamble_code = MOO_METHOD_PREAMBLE_ENSURE; preamble_index = 0; } preamble_flags |= md->variadic; /* MOO_METHOD_PREAMBLE_FLAG_VARIADIC or MOO_METHOD_PREAMBLE_FLAG_LIBERAL */ if (md->type == MOO_METHOD_DUAL) preamble_flags |= MOO_METHOD_PREAMBLE_FLAG_DUAL; if (md->lenient) preamble_flags |= MOO_METHOD_PREAMBLE_FLAG_LENIENT; MOO_ASSERT (moo, MOO_OOI_IN_METHOD_PREAMBLE_INDEX_RANGE(preamble_index)); return MOO_METHOD_MAKE_PREAMBLE(preamble_code, preamble_index, preamble_flags); } static void add_method_info_to_dbgi (moo_t* moo, moo_method_data_t* md, moo_oow_t class_offset, moo_oow_t* file_offset, moo_oow_t* method_offset) { const moo_ooch_t* file_name; MOO_ASSERT (moo, moo->dbgi != MOO_NULL); file_name = md->start_loc.file; if (!file_name) file_name = &_nul; if (moo_addfiletodbgi(moo, file_name, file_offset) <= -1) { /* TODO: warning */ *file_offset = 0; } else if (*file_offset > MOO_SMOOI_MAX) { /* TODO: warning */ *file_offset = 0; } /* TODO: preserve source text... */ if (moo_addmethodtodbgi(moo, *file_offset, class_offset, md->name.ptr, md->start_loc.line, md->code.locptr, md->code.len, MOO_NULL, 0, method_offset) <= -1) { /* TODO: warning. no debug information about this method will be available */ *method_offset = 0; } else if (*method_offset > MOO_SMOOI_MAX) { *method_offset = 0; } } static int add_compiled_method_to_class (moo_t* moo) { moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; moo_oop_char_t name; /* selector */ moo_oop_method_t mth; /* method */ moo_oow_t tmp_count = 0; moo_oow_t i; moo_ooi_t preamble; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_CLASS); name = (moo_oop_char_t)moo_makesymbol(moo, cc->mth.name.ptr, cc->mth.name.len); if (!name) goto oops; moo_pushvolat (moo, (moo_oop_t*)&name); tmp_count++; /* The variadic data part passed to moo_instantiate() is not GC-safe. * let's delay initialization of variadic data a bit. */ mth = (moo_oop_method_t)moo_instantiatewithtrailer(moo, moo->_method, cc->mth.literals.count, cc->mth.code.ptr, cc->mth.code.len); if (!mth) goto oops; for (i = 0; i < cc->mth.literals.count; i++) { /* let's do the variadic data initialization here - fill the literal frame */ MOO_STORE_OOP (moo, &mth->literal_frame[i], cc->mth.literals.ptr[i]); } moo_pushvolat (moo, (moo_oop_t*)&mth); tmp_count++; preamble = compute_preamble(moo, &cc->mth); MOO_STORE_OOP (moo, (moo_oop_t*)&mth->owner, (moo_oop_t)cc->self_oop); MOO_STORE_OOP (moo, (moo_oop_t*)&mth->name, (moo_oop_t)name); mth->preamble = MOO_SMOOI_TO_OOP(preamble); mth->preamble_data[0] = MOO_SMPTR_TO_OOP(0); mth->preamble_data[1] = MOO_SMPTR_TO_OOP(0); mth->tmpr_count = MOO_SMOOI_TO_OOP(cc->mth.tmpr_count); mth->tmpr_nargs = MOO_SMOOI_TO_OOP(cc->mth.tmpr_nargs); if (moo->dbgi) { moo_oow_t file_offset; moo_oow_t method_offset; add_method_info_to_dbgi (moo, &cc->mth, cc->dbgi_class_offset, &file_offset, &method_offset); mth->dbgi_file_offset = MOO_SMOOI_TO_OOP(file_offset); mth->dbgi_method_offset = MOO_SMOOI_TO_OOP(method_offset); } #if defined(MOO_DEBUG_COMPILER) moo_decode (moo, mth, &cc->fqn); #endif if (cc->mth.type == MOO_METHOD_DUAL) { if (!moo_putatdic(moo, cc->self_oop->mthdic[MOO_METHOD_INSTANCE], (moo_oop_t)name, (moo_oop_t)mth)) goto oops; if (!moo_putatdic(moo, cc->self_oop->mthdic[MOO_METHOD_CLASS], (moo_oop_t)name, (moo_oop_t)mth)) { /* 'name' is a symbol created of cc->mth.name. so use it as a key for deletion */ moo_deletedic (moo, cc->self_oop->mthdic[MOO_METHOD_INSTANCE], &cc->mth.name); goto oops; } } else { MOO_ASSERT (moo, cc->mth.type < MOO_COUNTOF(cc->self_oop->mthdic)); if (!moo_putatdic(moo, cc->self_oop->mthdic[cc->mth.type], (moo_oop_t)name, (moo_oop_t)mth)) goto oops; } moo_popvolats (moo, tmp_count); tmp_count = 0; return 0; oops: moo_popvolats (moo, tmp_count); return -1; } static int add_compiled_method_to_interface (moo_t* moo) { moo_cunit_interface_t* ifce = (moo_cunit_interface_t*)moo->c->cunit; moo_oop_char_t name; /* selector */ moo_oop_method_t mth; /* method signature with body */ moo_ooi_t preamble_flags = 0; moo_oow_t tmp_count = 0; moo_oow_t i; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_INTERFACE); name = (moo_oop_char_t)moo_makesymbol(moo, ifce->mth.name.ptr, ifce->mth.name.len); if (!name) goto oops; moo_pushvolat (moo, (moo_oop_t*)&name); tmp_count++; /* The variadic data part passed to moo_instantiate() is not GC-safe. * let's delay initialization of variadic data a bit. */ mth = (moo_oop_method_t)moo_instantiatewithtrailer(moo, moo->_method, ifce->mth.literals.count, ifce->mth.code.ptr, ifce->mth.code.len); if (!mth) goto oops; for (i = 0; i < ifce->mth.literals.count; i++) { /* let's do the variadic data initialization here - fill the literal frame */ MOO_STORE_OOP (moo, &mth->literal_frame[i], ifce->mth.literals.ptr[i]); } moo_pushvolat (moo, (moo_oop_t*)&mth); tmp_count++; MOO_STORE_OOP (moo, (moo_oop_t*)&mth->owner, (moo_oop_t)ifce->self_oop); MOO_STORE_OOP (moo, (moo_oop_t*)&mth->name, (moo_oop_t)name); preamble_flags |= ifce->mth.variadic; /* MOO_METHOD_PREAMBLE_FLAG_VARIADIC or MOO_METHOD_PREAMBLE_FLAG_LIBERAL */ if (ifce->mth.lenient) preamble_flags |= MOO_METHOD_PREAMBLE_FLAG_LENIENT; if (ifce->mth.type == MOO_METHOD_DUAL) preamble_flags |= MOO_METHOD_PREAMBLE_FLAG_DUAL; mth->preamble = MOO_SMOOI_TO_OOP(MOO_METHOD_MAKE_PREAMBLE(0,0,preamble_flags)); mth->preamble_data[0] = MOO_SMPTR_TO_OOP(0); mth->preamble_data[1] = MOO_SMPTR_TO_OOP(0); mth->tmpr_count = MOO_SMOOI_TO_OOP(ifce->mth.tmpr_count); mth->tmpr_nargs = MOO_SMOOI_TO_OOP(ifce->mth.tmpr_nargs); if (moo->dbgi) { moo_oow_t file_offset; moo_oow_t method_offset; add_method_info_to_dbgi (moo, &ifce->mth, ifce->dbgi_interface_offset, &file_offset, &method_offset); mth->dbgi_file_offset = MOO_SMOOI_TO_OOP(file_offset); mth->dbgi_method_offset = MOO_SMOOI_TO_OOP(method_offset); } #if defined(MOO_DEBUG_COMPILER) moo_decode (moo, mth, &ifce->fqn); #endif if (ifce->mth.type == MOO_METHOD_DUAL) { if (!moo_putatdic(moo, ifce->self_oop->mthdic[MOO_METHOD_INSTANCE], (moo_oop_t)name, (moo_oop_t)mth)) goto oops; if (!moo_putatdic(moo, ifce->self_oop->mthdic[MOO_METHOD_CLASS], (moo_oop_t)name, (moo_oop_t)mth)) { /* 'name' is a symbol created of ifce->mth.name. so use it as a key for deletion */ moo_deletedic (moo, ifce->self_oop->mthdic[MOO_METHOD_INSTANCE], &ifce->mth.name); goto oops; } } else { MOO_ASSERT (moo, ifce->mth.type < MOO_COUNTOF(ifce->self_oop->mthdic)); if (!moo_putatdic(moo, ifce->self_oop->mthdic[ifce->mth.type], (moo_oop_t)name, (moo_oop_t)mth)) goto oops; } moo_popvolats (moo, tmp_count); tmp_count = 0; return 0; oops: moo_popvolats (moo, tmp_count); return -1; } static int add_method_signature (moo_t* moo) { moo_cunit_interface_t* ifce = (moo_cunit_interface_t*)moo->c->cunit; moo_oop_char_t name; /* selector */ moo_oop_methsig_t mth; /* method signature */ moo_ooi_t preamble_flags = 0; moo_oow_t tmp_count = 0; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_INTERFACE); name = (moo_oop_char_t)moo_makesymbol(moo, ifce->mth.name.ptr, ifce->mth.name.len); if (!name) goto oops; moo_pushvolat (moo, (moo_oop_t*)&name); tmp_count++; mth = (moo_oop_methsig_t)moo_instantiate(moo, moo->_methsig, MOO_NULL, 0); if (!mth) goto oops; moo_pushvolat (moo, (moo_oop_t*)&mth); tmp_count++; MOO_STORE_OOP (moo, (moo_oop_t*)&mth->owner, (moo_oop_t)ifce->self_oop); MOO_STORE_OOP (moo, (moo_oop_t*)&mth->name, (moo_oop_t)name); preamble_flags |= ifce->mth.variadic; /* MOO_METHOD_PREAMBLE_FLAG_VARIADIC or MOO_METHOD_PREAMBLE_FLAG_LIBERAL */ if (ifce->mth.lenient) preamble_flags |= MOO_METHOD_PREAMBLE_FLAG_LENIENT; if (ifce->mth.type == MOO_METHOD_DUAL) preamble_flags |= MOO_METHOD_PREAMBLE_FLAG_DUAL; mth->preamble = MOO_SMOOI_TO_OOP(MOO_METHOD_MAKE_PREAMBLE(0,0,preamble_flags)); mth->tmpr_nargs = MOO_SMOOI_TO_OOP(ifce->mth.tmpr_nargs); if (ifce->mth.type == MOO_METHOD_DUAL) { if (!moo_putatdic(moo, ifce->self_oop->mthdic[MOO_METHOD_INSTANCE], (moo_oop_t)name, (moo_oop_t)mth)) goto oops; if (!moo_putatdic(moo, ifce->self_oop->mthdic[MOO_METHOD_CLASS], (moo_oop_t)name, (moo_oop_t)mth)) { /* 'name' is a symbol created of ifce->mth.name. so use it as a key for deletion */ moo_deletedic (moo, ifce->self_oop->mthdic[MOO_METHOD_INSTANCE], &ifce->mth.name); goto oops; } } else { MOO_ASSERT (moo, ifce->mth.type < MOO_COUNTOF(ifce->self_oop->mthdic)); if (!moo_putatdic(moo, ifce->self_oop->mthdic[ifce->mth.type], (moo_oop_t)name, (moo_oop_t)mth)) goto oops; } moo_popvolats (moo, tmp_count); tmp_count = 0; return 0; oops: moo_popvolats (moo, tmp_count); return -1; } static void clear_pooldic_import_data (moo_t* moo, moo_pooldic_import_data_t* pdimp) { if (pdimp->dcl.ptr) moo_freemem (moo, pdimp->dcl.ptr); if (pdimp->dics.ptr) moo_freemem (moo, pdimp->dics.ptr); MOO_MEMSET (pdimp, 0, MOO_SIZEOF(*pdimp)); } static void clear_method_data (moo_t* moo, moo_method_data_t* mth) { if (mth->text.ptr) moo_freemem (moo, mth->text.ptr); if (mth->assignees.ptr) moo_freemem (moo, mth->assignees.ptr); if (mth->binsels.ptr) moo_freemem (moo, mth->binsels.ptr); if (mth->kwsels.ptr) moo_freemem (moo, mth->kwsels.ptr); if (mth->name.ptr) moo_freemem (moo, mth->name.ptr); if (mth->tmprs.ptr) moo_freemem (moo, mth->tmprs.ptr); if (mth->code.ptr) moo_freemem (moo, mth->code.ptr); if (mth->code.locptr) moo_freemem (moo, mth->code.locptr); if (mth->literals.ptr) moo_freemem (moo, mth->literals.ptr); if (mth->blk_tmprcnt) moo_freemem (moo, mth->blk_tmprcnt); /* this must not happen as loop data are cleared in functions that handle loop expressions(). * but let me have this here just in case */ while (mth->loop) pop_loop (moo); while (mth->_label) { moo_label_t* tmp = mth->_label; mth->_label = mth->_label->next; moo_freemem (moo, tmp); } while (mth->_goto) { moo_goto_t* tmp = mth->_goto; mth->_goto = mth->_goto->next; moo_freemem (moo, tmp); } MOO_MEMSET (mth, 0, MOO_SIZEOF(*mth)); } static void reset_method_data (moo_t* moo, moo_method_data_t* mth) { /* unlike clear_method_data(), this function doesn't free memory allocated */ mth->type = MOO_METHOD_INSTANCE; mth->primitive = 0; mth->lenient = 0; mth->text.len = 0; mth->assignees.len = 0; mth->binsels.len = 0; mth->kwsels.len = 0; mth->name.len = 0; MOO_MEMSET (&mth->name_loc, 0, MOO_SIZEOF(mth->name_loc)); MOO_MEMSET (&mth->start_loc, 0, MOO_SIZEOF(mth->start_loc)); mth->variadic = 0; mth->tmprs.len = 0; mth->tmpr_count = 0; mth->tmpr_nargs = 0; mth->literals.count = 0; mth->pftype = PFTYPE_NONE; mth->pfnum = 0; mth->blk_idseq = 0; mth->blk_id = 0; mth->blk_depth = 0; /* don't reset mth->blk_tmprcnt_capa as it indicates capacity for blk_tmprcnt. * mth->blk_depth indicates the number of elements in mth->blk_tmprcnt_capa. * this function doesn't free the allocated memory pointed to by mth->blk->tmprcnt. * see store_tmpr_count_for_block(). */ mth->code.len = 0; MOO_ASSERT (moo, mth->loop == MOO_NULL); /* the current implementation allocates a label and a goto chunk for each * label and goto statement. it prevents the reuse of the allocated chunks * without major code change.. so let's free these here which is confliting * with the purpose of this function */ while (mth->_label) { moo_label_t* tmp = mth->_label; mth->_label = mth->_label->next; moo_freemem (moo, tmp); } while (mth->_goto) { moo_goto_t* tmp = mth->_goto; mth->_goto = mth->_goto->next; moo_freemem (moo, tmp); } } static int process_method_modifiers (moo_t* moo) { moo_method_data_t* md = get_cunit_method_data(moo); GET_TOKEN (moo); if (TOKEN_TYPE(moo) != MOO_IOTOK_RPAREN) { do { if (is_token_symbol(moo, VOCA_CLASS_S)) { /* method(#class) */ if (md->type == MOO_METHOD_CLASS || md->type == MOO_METHOD_DUAL) { moo_setsynerr (moo, MOO_SYNERR_MODIFIERDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } md->type = MOO_METHOD_CLASS; GET_TOKEN (moo); } else if (is_token_symbol(moo, VOCA_DUAL_S)) { /* method(#dual) */ if (md->type == MOO_METHOD_CLASS || md->type == MOO_METHOD_DUAL) { moo_setsynerr (moo, MOO_SYNERR_MODIFIERDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } md->type = MOO_METHOD_DUAL; GET_TOKEN (moo); } else if (is_token_symbol(moo, VOCA_PRIMITIVE_S)) { /* method(#primitive) */ if (md->primitive) { /* #primitive duplicate modifier */ moo_setsynerr (moo, MOO_SYNERR_MODIFIERDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } md->primitive = 1; GET_TOKEN (moo); } else if (is_token_symbol(moo, VOCA_LENIENT_S)) { /* method(#lenient) */ if (md->lenient) { moo_setsynerr (moo, MOO_SYNERR_MODIFIERDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } md->lenient = 1; GET_TOKEN (moo); } else if (is_token_symbol(moo, VOCA_VARIADIC_S) || is_token_symbol(moo, VOCA_LIBERAL_S)) { /* method(#variadic) or method(#liberal) */ if (md->variadic) { /* #variadic duplicate modifier */ moo_setsynerr (moo, MOO_SYNERR_MODIFIERDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (is_token_symbol(moo, VOCA_LIBERAL_S)) md->variadic = MOO_METHOD_PREAMBLE_FLAG_LIBERAL; else md->variadic = MOO_METHOD_PREAMBLE_FLAG_VARIADIC; GET_TOKEN (moo); } else if (TOKEN_TYPE(moo) == MOO_IOTOK_COMMA || TOKEN_TYPE(moo) == MOO_IOTOK_EOF || TOKEN_TYPE(moo) == MOO_IOTOK_RPAREN) { /* no modifier is present */ moo_setsynerr (moo, MOO_SYNERR_MODIFIER, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } else { /* invalid modifier */ moo_setsynerr (moo, MOO_SYNERR_MODIFIERINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (TOKEN_TYPE(moo) != MOO_IOTOK_COMMA) break; /* hopefully ) */ GET_TOKEN (moo); /* get the token after , */ } while (1); } if (TOKEN_TYPE(moo) != MOO_IOTOK_RPAREN) { /* ) expected */ moo_setsynerr (moo, MOO_SYNERR_RPAREN, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); return 0; } static int resolve_primitive_method (moo_t* moo) { moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; moo_oocs_t mthname; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_CLASS); /* the primitive method must be of this form * method(#primitive) method_name. */ if (TOKEN_TYPE(moo) != MOO_IOTOK_PERIOD) { /* . expected */ moo_setsynerr (moo, MOO_SYNERR_PERIOD, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } /* * remove all leading underscores from the method name when building a primitive * identifer. multiple methods can map to the same primitive handler. * for class X, you may have method(#primitive) aa and method(#primitive) _aa * to map to the X_aa primitive handler. */ mthname = cc->mth.name; while (mthname.len > 0) { if (*mthname.ptr != '_') break; mthname.ptr++; mthname.len--; } if (mthname.len == 0) { MOO_DEBUG2 (moo, "Invalid primitive function name - %.*js\n", cc->mth.name.len, cc->mth.name.ptr); moo_setsynerr (moo, MOO_SYNERR_PFIDINVAL, &cc->mth.name_loc, &cc->mth.name); return -1; } if (cc->self_oop->modname == moo->_nil) { /* no module name specified in the class definition using 'from'. * it's a builtin primitive function */ moo_oow_t savedlen; moo_ooi_t pfnum; moo_pfbase_t* pfbase; /* primitive identifer = classname_methodname */ /* compose the identifer into the back of the cls.modname buffer. * i'll revert it when done. */ savedlen = cc->modname.len; if (copy_string_to(moo, &cc->name, &cc->modname, &cc->modname_capa, 1, '\0') <= -1 || copy_string_to(moo, &mthname, &cc->modname, &cc->modname_capa, 1, '_') <= -1) { cc->modname.len = savedlen; return -1; } pfbase = moo_getpfnum(moo, &cc->modname.ptr[savedlen], cc->modname.len - savedlen, &pfnum); if (!pfbase) { MOO_DEBUG2 (moo, "Cannot find intrinsic primitive function - %.*js\n", cc->modname.len - savedlen, &cc->modname.ptr[savedlen]); moo_setsynerr (moo, MOO_SYNERR_PFIDINVAL, &cc->mth.name_loc, &cc->mth.name); cc->modname.len = savedlen; return -1; } if (cc->mth.tmpr_nargs < pfbase->minargs || cc->mth.tmpr_nargs > pfbase->maxargs) { MOO_DEBUG5 (moo, "Unsupported argument count in primitive method definition of %.*js - %zd-%zd expected, %zd specified\n", cc->modname.len - savedlen, &cc->modname.ptr[savedlen], pfbase->minargs, pfbase->maxargs, cc->mth.tmpr_nargs); moo_setsynerr (moo, MOO_SYNERR_PFARGDEFINVAL, &cc->mth.name_loc, &cc->mth.name); cc->modname.len = savedlen; return -1; } cc->modname.len = savedlen; MOO_ASSERT (moo, MOO_OOI_IN_METHOD_PREAMBLE_INDEX_RANGE(pfnum)); cc->mth.pftype = PFTYPE_NUMBERED; cc->mth.pfnum = pfnum; } else { moo_oow_t litidx, savedlen; moo_oocs_t tmp; moo_pfbase_t* pfbase; /* combine the module name and the method name delimited by a period * when doing it, let me reuse the cls.modname buffer and restore it * back once done */ savedlen = cc->modname.len; MOO_ASSERT (moo, MOO_CLASSOF(moo, cc->self_oop->modname) == moo->_symbol); tmp.ptr = MOO_OBJ_GET_CHAR_SLOT(cc->self_oop->modname); tmp.len = MOO_OBJ_GET_SIZE(cc->self_oop->modname); if (copy_string_to (moo, &tmp, &cc->modname, &cc->modname_capa, 1, '\0') <= -1 || copy_string_to (moo, &mthname, &cc->modname, &cc->modname_capa, 1, '.') <= -1 || add_symbol_literal(moo, &cc->modname, savedlen, &litidx) <= -1) { cc->modname.len = savedlen; return -1; } /* check if the primitive function exists at the compile time and perform some checks. * see compile_method_primitive() for similar checks */ pfbase = moo_querymodpf(moo, &cc->modname.ptr[savedlen], cc->modname.len - savedlen, MOO_NULL); if (!pfbase) { MOO_DEBUG2 (moo, "Cannot find module primitive function - %.*js\n", cc->modname.len - savedlen, &cc->modname.ptr[savedlen]); moo_setsynerr (moo, MOO_SYNERR_PFIDINVAL, &cc->mth.name_loc, &cc->mth.name); cc->modname.len = savedlen; return -1; } if (cc->mth.tmpr_nargs < pfbase->minargs || cc->mth.tmpr_nargs > pfbase->maxargs) { MOO_DEBUG5 (moo, "Unsupported argument count in primitive method definition of %.*js - %zd-%zd expected, %zd specified\n", cc->modname.len - savedlen, &cc->modname.ptr[savedlen], pfbase->minargs, pfbase->maxargs, cc->mth.tmpr_nargs); moo_setsynerr (moo, MOO_SYNERR_PFARGDEFINVAL, &cc->mth.name_loc, &cc->mth.name); cc->modname.len = savedlen; return -1; } cc->modname.len = savedlen; /* the symbol added must be the first literal to the current method. * so this condition must be true. */ MOO_ASSERT (moo, MOO_OOI_IN_METHOD_PREAMBLE_INDEX_RANGE(litidx)); /* external named primitive containing a period. */ cc->mth.pftype = PFTYPE_NAMED; cc->mth.pfnum = litidx; } return 0; } static int __compile_method_definition (moo_t* moo) { moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_CLASS); if (TOKEN_TYPE(moo) == MOO_IOTOK_LPAREN) { /* process method modifiers */ if (process_method_modifiers(moo) <= -1) return -1; } if (compile_method_name(moo) <= -1) return -1; if (cc->mth.primitive) { /* the primitive method doesn't have body */ if (resolve_primitive_method(moo) <= -1) return -1; } else { if (TOKEN_TYPE(moo) != MOO_IOTOK_LBRACE) { /* { expected */ moo_setsynerr (moo, MOO_SYNERR_LBRACE, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); if (compile_method_temporaries(moo) <= -1 || compile_method_pragma(moo) <= -1 || compile_method_statements(moo) <= -1) return -1; if (TOKEN_TYPE(moo) != MOO_IOTOK_RBRACE) { /* } expected */ moo_setsynerr (moo, MOO_SYNERR_RBRACE, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } /* end of method has been reached */ if (resolve_goto_labels(moo) <= -1) return -1; } GET_TOKEN (moo); /* add a compiled method to the method dictionary */ if (add_compiled_method_to_class(moo) <= -1) return -1; return 0; } static int compile_method_definition (moo_t* moo) { moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; int n; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_CLASS); MOO_ASSERT (moo, moo->c->balit.count == 0); MOO_ASSERT (moo, moo->c->arlit.count == 0); /* clear data required to compile a method */ cc->mth.active = 1; reset_method_data (moo, &cc->mth); cc->mth.start_loc = *TOKEN_LOC(moo); n = __compile_method_definition(moo); cc->mth.active = 0; return n; } static int make_getter_method (moo_t* moo, const moo_oocs_t* name, const var_info_t* var) { moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_CLASS); MOO_ASSERT (moo, moo->c->balit.count == 0); MOO_ASSERT (moo, moo->c->arlit.count == 0); MOO_ASSERT (moo, cc->mth.name.len == 0); if (add_method_name_fragment(moo, name) <= -1) return -1; switch (var->type) { case VAR_INSTANCE: MOO_ASSERT (moo, cc->mth.type == MOO_METHOD_INSTANCE); if (emit_single_param_instruction(moo, BCODE_PUSH_INSTVAR_0, var->pos, MOO_NULL) <= -1 || emit_byte_instruction(moo, BCODE_RETURN_STACKTOP, MOO_NULL) <= -1) return -1; break; case VAR_CLASSINST: MOO_ASSERT (moo, cc->mth.type == MOO_METHOD_CLASS); if (emit_single_param_instruction(moo, BCODE_PUSH_INSTVAR_0, var->pos, MOO_NULL) <= -1 || emit_byte_instruction(moo, BCODE_RETURN_STACKTOP, MOO_NULL) <= -1) return -1; break; case VAR_CLASS: { moo_oow_t index; MOO_ASSERT (moo, var->cls != MOO_NULL); MOO_ASSERT (moo, var->cls == cc->self_oop); MOO_ASSERT (moo, cc->mth.type == MOO_METHOD_CLASS); if (add_literal(moo, (moo_oop_t)var->cls, &index) <= -1 || emit_double_param_instruction(moo, BCODE_PUSH_OBJVAR_0, var->pos, index, MOO_NULL) <= -1 || emit_byte_instruction(moo, BCODE_RETURN_STACKTOP, MOO_NULL) <= -1) return -1; break; } default: MOO_DEBUG1 (moo, "internal error - invalid variable type in make_getter_method - %d\n", (int)var->type); moo_seterrnum (moo, MOO_EINTERN); return -1; } return add_compiled_method_to_class(moo); } static int make_setter_method (moo_t* moo, const moo_oocs_t* name, const var_info_t* var) { moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; static moo_ooch_t colon = ':'; static moo_oocs_t colons = { &colon, 1 }; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_CLASS); MOO_ASSERT (moo, moo->c->balit.count == 0); MOO_ASSERT (moo, moo->c->arlit.count == 0); MOO_ASSERT (moo, cc->mth.name.len == 0); if (add_method_name_fragment(moo, name) <= -1 || add_method_name_fragment(moo, &colons) <= -1) return -1; switch (var->type) { case VAR_INSTANCE: MOO_ASSERT (moo, cc->mth.type == MOO_METHOD_INSTANCE); if (emit_single_param_instruction(moo, BCODE_PUSH_TEMPVAR_0, 0, MOO_NULL) <= -1 || emit_single_param_instruction(moo, BCODE_POP_INTO_INSTVAR_0, var->pos, MOO_NULL) <= -1 || emit_byte_instruction(moo, BCODE_RETURN_RECEIVER, MOO_NULL) <= -1) return -1; break; case VAR_CLASSINST: MOO_ASSERT (moo, cc->mth.type == MOO_METHOD_CLASS); if (emit_single_param_instruction(moo, BCODE_PUSH_TEMPVAR_0, 0, MOO_NULL) <= -1 || emit_single_param_instruction(moo, BCODE_POP_INTO_INSTVAR_0, var->pos, MOO_NULL) <= -1 || emit_byte_instruction(moo, BCODE_RETURN_RECEIVER, MOO_NULL) <= -1) return -1; break; case VAR_CLASS: { moo_oow_t index; MOO_ASSERT (moo, var->cls != MOO_NULL); MOO_ASSERT (moo, var->cls == cc->self_oop); MOO_ASSERT (moo, cc->mth.type == MOO_METHOD_CLASS); if (add_literal(moo, (moo_oop_t)var->cls, &index) <= -1 || emit_single_param_instruction(moo, BCODE_PUSH_TEMPVAR_0, 0, MOO_NULL) <= -1 || emit_double_param_instruction(moo, BCODE_POP_INTO_OBJVAR_0, var->pos, index, MOO_NULL) <= -1 || emit_byte_instruction(moo, BCODE_RETURN_RECEIVER, MOO_NULL) <= -1) return -1; break; } default: MOO_DEBUG1 (moo, "internal error - invalid variable type in make_setter_method - %d\n", (int)var->type); moo_seterrnum (moo, MOO_EINTERN); return -1; } return add_compiled_method_to_class(moo); } static int make_getters_and_setters (moo_t* moo) { moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; moo_oow_t i, var_type; moo_oocs_t var_name; moo_ioloc_t fake_loc; var_info_t var_info; int x; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_CLASS); fake_loc.line = 0; fake_loc.colm = 0; for (var_type = VAR_INSTANCE; var_type <= VAR_CLASS; var_type++) { for (i = 0; i < cc->var[var_type].initv_count; i++) { if (!cc->var[var_type].initv[i].flags) continue; /* cc->mth.type needs to be set because get_variable_info() * uses it to validate variable's accessibility */ reset_method_data (moo, &cc->mth); cc->mth.type = (var_type == VAR_INSTANCE? MOO_METHOD_INSTANCE: MOO_METHOD_CLASS); /* the following two function calls must not fail unless the compiler * is buggy. */ x = fetch_word_from_string(&cc->var[var_type].str, i, &var_name); MOO_ASSERT (moo, x >= 0); x = get_variable_info(moo, &var_name, &fake_loc, 0, &var_info); MOO_ASSERT (moo, x >= 0); MOO_ASSERT (moo, var_info.type == var_type); if (cc->var[var_type].initv[i].flags & VARACC_GETTER) { /* the method data has been reset above. */ if (make_getter_method(moo, &var_name, &var_info) <= -1) return -1; } if (cc->var[var_type].initv[i].flags & VARACC_SETTER) { /* i set the method data here because make_getter_method() * pollutes it if triggered */ reset_method_data (moo, &cc->mth); cc->mth.type = (var_type == VAR_INSTANCE? MOO_METHOD_INSTANCE: MOO_METHOD_CLASS); /* hack to simulate a parameter. note i don't manipulate tmprs or tmprs_capa * because there is no method body code to process. i simply generate a setter * method */ cc->mth.tmpr_count = 1; cc->mth.tmpr_nargs = 1; MOO_ASSERT (moo, var_info.type == var_type); if (make_setter_method(moo, &var_name, &var_info) <= -1) return -1; } } } return 0; } static int make_default_initial_values (moo_t* moo, var_type_t var_type) { moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; moo_oow_t initv_count, super_initv_count; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_CLASS); MOO_ASSERT (moo, var_type == VAR_INSTANCE || var_type == VAR_CLASSINST); MOO_ASSERT (moo, VAR_INSTANCE == 0); MOO_ASSERT (moo, VAR_CLASSINST == 1); initv_count = cc->var[var_type].initv_count; if (cc->super_oop != moo->_nil && ((moo_oop_class_t)cc->super_oop)->initv[var_type] != moo->_nil) { super_initv_count = MOO_OBJ_GET_SIZE(((moo_oop_class_t)cc->super_oop)->initv[var_type]); } else { super_initv_count = 0; } initv_count += super_initv_count; if (initv_count > 0) { moo_oow_t i, j; moo_oop_t tmp; /* [NOTE] * if some elements at the back of a class definition are lacking default values, * initv_count is less than total_count. * in the following case(no inheritance for simplicity): * class ... { var a, b := 10, c. } * initv_count is 1 whereas total_count is 3. */ MOO_ASSERT (moo, initv_count <= cc->var[var_type].total_count); tmp = moo_instantiate(moo, moo->_array, MOO_NULL, cc->var[var_type].total_count); if (!tmp) return -1; if (super_initv_count > 0) { /* handle default values defined in the superclass chain. * i merge them into a single array for convenience and * efficiency of object instantiation by moo_instantiate(). * it can avoid looking up superclasses upon instantiaion */ moo_oop_oop_t initv; j = 0; MOO_ASSERT (moo, cc->super_oop != moo->_nil); initv = (moo_oop_oop_t)((moo_oop_class_t)cc->super_oop)->initv[var_type]; MOO_ASSERT (moo, MOO_CLASSOF(moo, initv) == moo->_array); for (i = 0; i < super_initv_count; i++) { if (MOO_OBJ_GET_OOP_VAL(initv, i)) MOO_STORE_OOP (moo, MOO_OBJ_GET_OOP_PTR(tmp, j), MOO_OBJ_GET_OOP_VAL(initv, i)); j++; } } else { /* superclass chain have variables but no default values are defined */ j = cc->var[var_type].total_count - cc->var[var_type].count; } for (i = 0; i < cc->var[var_type].initv_count; i++) { if (cc->var[var_type].initv[i].v) MOO_STORE_OOP (moo, MOO_OBJ_GET_OOP_PTR(tmp, j), cc->var[var_type].initv[i].v); j++; } cc->self_oop->initv[var_type] = tmp; } return 0; } static int make_defined_class (moo_t* moo) { /* this function makes a class object with no functions/methods */ moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; moo_oop_t tmp; moo_ooi_t spec, self_spec; int just_made = 0, flags; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_CLASS); flags = 0; if (cc->flags & CLASS_INDEXED) flags |= MOO_CLASS_SPEC_FLAG_INDEXED; if (cc->flags & CLASS_IMMUTABLE) flags |= MOO_CLASS_SPEC_FLAG_IMMUTABLE; if (cc->flags & CLASS_UNCOPYABLE) flags |= MOO_CLASS_SPEC_FLAG_UNCOPYABLE; if (cc->non_pointer_instsize > 0) { /* class(#byte(N)), class(#word(N)), etc */ MOO_ASSERT (moo, cc->var[VAR_INSTANCE].total_count == 0); MOO_ASSERT (moo, cc->flags & CLASS_INDEXED); MOO_ASSERT (moo, cc->indexed_type != MOO_OBJ_TYPE_OOP); spec = MOO_CLASS_SPEC_MAKE(cc->non_pointer_instsize, flags, cc->indexed_type); } else { MOO_ASSERT (moo, cc->non_pointer_instsize == 0); spec = MOO_CLASS_SPEC_MAKE(cc->var[VAR_INSTANCE].total_count, flags, cc->indexed_type); } flags = 0; if (cc->flags & CLASS_FINAL) flags |= MOO_CLASS_SELFSPEC_FLAG_FINAL; if (cc->flags & CLASS_LIMITED) flags |= MOO_CLASS_SELFSPEC_FLAG_LIMITED; self_spec = MOO_CLASS_SELFSPEC_MAKE(cc->var[VAR_CLASS].total_count, cc->var[VAR_CLASSINST].total_count, flags); if (cc->self_oop) { /* this is an internally created class object being defined. */ MOO_ASSERT (moo, MOO_CLASSOF(moo, cc->self_oop) == moo->_class); MOO_ASSERT (moo, MOO_OBJ_GET_FLAGS_KERNEL(cc->self_oop) == MOO_OBJ_FLAGS_KERNEL_IMMATURE); if (spec != MOO_OOP_TO_SMOOI(cc->self_oop->spec) || self_spec != MOO_OOP_TO_SMOOI(cc->self_oop->selfspec)) { /* it conflicts with internal definition */ moo_setsynerr (moo, MOO_SYNERR_CLASSCONTRA, &cc->fqn_loc, &cc->name); return -1; } } else { /* the class variables and class instance variables are placed * inside the class object after the fixed part. */ tmp = moo_instantiate(moo, moo->_class, MOO_NULL, cc->var[VAR_CLASSINST].total_count + cc->var[VAR_CLASS].total_count); if (!tmp) return -1; just_made = 1; MOO_STORE_OOP (moo, (moo_oop_t*)&cc->self_oop, tmp); MOO_ASSERT (moo, MOO_CLASSOF(moo, cc->self_oop) == moo->_class); cc->self_oop->spec = MOO_SMOOI_TO_OOP(spec); cc->self_oop->selfspec = MOO_SMOOI_TO_OOP(self_spec); } /* TODO: check if the current class definition conflicts with the superclass. * if superclass is byte variable, the current class cannot be word variable or something else. * TODO: TODO: TODO: */ MOO_OBJ_SET_FLAGS_KERNEL (cc->self_oop, MOO_OBJ_FLAGS_KERNEL_MATURE); MOO_STORE_OOP (moo, &cc->self_oop->superclass, cc->super_oop); if (just_made) { /* set the name of a class if it's not set. at this point, * only kernel classes must have a name which has been set * during ignition phase. See ignite_3() */ tmp = moo_makesymbol(moo, cc->name.ptr, cc->name.len); if (!tmp) return -1; MOO_STORE_OOP (moo, (moo_oop_t*)&cc->self_oop->name, tmp); } MOO_ASSERT (moo, (moo_oop_t)cc->self_oop->name != moo->_nil); if (cc->modname.len > 0) { tmp = moo_makesymbol(moo, cc->modname.ptr, cc->modname.len); if (!tmp) return -1; MOO_STORE_OOP (moo, (moo_oop_t*)&cc->self_oop->modname, tmp); } tmp = moo_makestring(moo, cc->var[VAR_INSTANCE].str.ptr, cc->var[VAR_INSTANCE].str.len); if (!tmp) return -1; MOO_STORE_OOP (moo, (moo_oop_t*)&cc->self_oop->instvars, tmp); tmp = moo_makestring(moo, cc->var[VAR_CLASS].str.ptr, cc->var[VAR_CLASS].str.len); if (!tmp) return -1; MOO_STORE_OOP (moo, (moo_oop_t*)&cc->self_oop->classvars, tmp); tmp = moo_makestring(moo, cc->var[VAR_CLASSINST].str.ptr, cc->var[VAR_CLASSINST].str.len); if (!tmp) return -1; MOO_STORE_OOP (moo, (moo_oop_t*)&cc->self_oop->classinstvars, tmp); /* * this is done at the end of __compile_class_definition() tmp = moo_makestring(moo, cc->pdimp.dcl.ptr, cc->pdimp.dcl.len); if (!tmp) return -1; MOO_STORE_OOP (moo, (moo_oop_t*)&cc->self_oop->pooldics, tmp); */ tmp = (moo_oop_t)moo_makedic(moo, moo->_method_dictionary, INSTANCE_METHOD_DICTIONARY_SIZE); if (!tmp) return -1; MOO_STORE_OOP (moo, (moo_oop_t*)&cc->self_oop->mthdic[MOO_METHOD_INSTANCE], tmp); tmp = (moo_oop_t)moo_makedic(moo, moo->_method_dictionary, CLASS_METHOD_DICTIONARY_SIZE); if (!tmp) return -1; MOO_STORE_OOP (moo, (moo_oop_t*)&cc->self_oop->mthdic[MOO_METHOD_CLASS], tmp); /* store the default intial values for instance variables */ if (make_default_initial_values(moo, VAR_INSTANCE) <= -1) return -1; /* store the default intial values for class instance variables */ if (make_default_initial_values(moo, VAR_CLASSINST) <= -1) return -1; if (cc->self_oop->initv[VAR_CLASSINST] != moo->_nil) { moo_oow_t i, initv_count; moo_oop_oop_t initv; /* apply the default initial values for class instance variables to this class now */ initv = (moo_oop_oop_t)cc->self_oop->initv[VAR_CLASSINST]; MOO_ASSERT (moo, MOO_CLASSOF(moo, initv) == moo->_array); initv_count = MOO_OBJ_GET_SIZE(initv); for (i = 0; i < initv_count; i++) { MOO_STORE_OOP (moo, &cc->self_oop->cvar[i], MOO_OBJ_GET_OOP_VAL(initv, i)); } } /* initialize class variables with default initial values */ if (cc->var[VAR_CLASS].initv_count > 0) { moo_oow_t i, j, initv_count; initv_count = cc->var[VAR_CLASS].initv_count; /* name instance variables and class instance variables are placed * in the front part. set j such that they can be skipped. */ j = cc->var[VAR_CLASSINST].total_count; MOO_ASSERT (moo, MOO_CLASS_NAMED_INSTVARS + j + initv_count <= MOO_OBJ_GET_SIZE(cc->self_oop)); for (i = 0; i < initv_count; i++) { MOO_STORE_OOP (moo, &cc->self_oop->cvar[j], cc->var[VAR_CLASS].initv[i].v); j++; } } /* [NOTE] don't create a dictionary on the nsdic. keep it to be nil. * add_nsdic_to_class() instantiates a dictionary if necessary. */ /* [NOTE] don't set the trsize field yet here. */ /* TODO: initialize more fields??? what else. */ /* TODO: update the subclasses field of the superclass if it's not nil */ if (just_made) { /* register the class to the system dictionary. kernel classes have * been registered at the ignition phase. */ if (!moo_putatdic(moo, (moo_oop_dic_t)cc->ns_oop, (moo_oop_t)cc->self_oop->name, (moo_oop_t)cc->self_oop)) return -1; MOO_STORE_OOP (moo, (moo_oop_t*)&cc->self_oop->nsup, (moo_oop_t)cc->ns_oop); /* the nsup field of the class must be set before a call to make_getters_and_setters(). * the function may print the full qualified name of the class if method dump * is performed. */ } return make_getters_and_setters(moo); } static MOO_INLINE int _set_class_indexed_type (moo_t* moo, moo_obj_type_t type) { moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_CLASS); if (cc->flags & CLASS_INDEXED) { moo_setsynerr (moo, MOO_SYNERR_MODIFIERDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } cc->flags |= CLASS_INDEXED; cc->indexed_type = type; return 0; } static int process_class_modifiers (moo_t* moo, moo_ioloc_t* type_loc) { moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_CLASS); GET_TOKEN (moo); if (TOKEN_TYPE(moo) != MOO_IOTOK_RPAREN) { do { int permit_non_pointer_instsize = 0; if (is_token_symbol(moo, VOCA_BYTE_S)) { /* class(#byte) */ if (_set_class_indexed_type(moo, MOO_OBJ_TYPE_BYTE) <= -1) return -1; GET_TOKEN (moo); permit_non_pointer_instsize = 1; } else if (is_token_symbol(moo, VOCA_CHARACTER_S)) { /* class(#character) */ if (_set_class_indexed_type(moo, MOO_OBJ_TYPE_CHAR) <= -1) return -1; GET_TOKEN (moo); permit_non_pointer_instsize = 1; } else if (is_token_symbol(moo, VOCA_HALFWORD_S)) { /* class(#halfword) */ if (_set_class_indexed_type(moo, MOO_OBJ_TYPE_HALFWORD) <= -1) return -1; GET_TOKEN (moo); permit_non_pointer_instsize = 1; } else if (is_token_symbol(moo, VOCA_WORD_S)) { /* class(#word) */ if (_set_class_indexed_type(moo, MOO_OBJ_TYPE_WORD) <= -1) return -1; GET_TOKEN (moo); permit_non_pointer_instsize = 1; } else if (is_token_symbol(moo, VOCA_LIWORD_S)) { /* class(#liword) - * the liword type maps to one of word or halfword. * see the definiton of MOO_OBJ_TYPE_LIWORD in moo.h */ if (_set_class_indexed_type(moo, MOO_OBJ_TYPE_LIWORD) <= -1) return -1; GET_TOKEN (moo); permit_non_pointer_instsize = 1; } else if (is_token_symbol(moo, VOCA_POINTER_S)) { /* class(#pointer) */ if (_set_class_indexed_type(moo, MOO_OBJ_TYPE_OOP) <= -1) return -1; GET_TOKEN (moo); } else if (is_token_symbol(moo, VOCA_FINAL_S)) { if (cc->flags & CLASS_FINAL) { moo_setsynerr (moo, MOO_SYNERR_MODIFIERDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } cc->flags |= CLASS_FINAL; GET_TOKEN(moo); } else if (is_token_symbol(moo, VOCA_LIMITED_S)) { if (cc->flags & CLASS_LIMITED) { moo_setsynerr (moo, MOO_SYNERR_MODIFIERDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } cc->flags |= CLASS_LIMITED; GET_TOKEN(moo); } else if (is_token_symbol(moo, VOCA_IMMUTABLE_S)) { if (cc->flags & CLASS_IMMUTABLE) { moo_setsynerr (moo, MOO_SYNERR_MODIFIERDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } cc->flags |= CLASS_IMMUTABLE; GET_TOKEN(moo); } else if (is_token_symbol(moo, VOCA_UNCOPYABLE_S)) { if (cc->flags & CLASS_UNCOPYABLE) { moo_setsynerr (moo, MOO_SYNERR_MODIFIERDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } cc->flags |= CLASS_UNCOPYABLE; GET_TOKEN(moo); } else if (TOKEN_TYPE(moo) == MOO_IOTOK_COMMA || TOKEN_TYPE(moo) == MOO_IOTOK_EOF || TOKEN_TYPE(moo) == MOO_IOTOK_RPAREN) { /* no modifier is present */ moo_setsynerr (moo, MOO_SYNERR_MODIFIER, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } else { /* invalid modifier */ moo_setsynerr (moo, MOO_SYNERR_MODIFIERINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (permit_non_pointer_instsize) { /* class(#byte(20)) * class(#word(3)) * ... */ *type_loc = moo->c->tok.loc; if (TOKEN_TYPE(moo) == MOO_IOTOK_LPAREN) { moo_ooi_t tmp; GET_TOKEN (moo); if (TOKEN_TYPE(moo) != MOO_IOTOK_INTLIT && TOKEN_TYPE(moo) != MOO_IOTOK_RADINTLIT) { moo_setsynerr (moo, MOO_SYNERR_LITERAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (string_to_smooi(moo, TOKEN_NAME(moo), TOKEN_TYPE(moo) == MOO_IOTOK_RADINTLIT, &tmp) <= -1 || tmp < 0 || tmp > MOO_MAX_NAMED_INSTVARS) { /* the class type size has nothing to do with the name instance variables * in the semantics. but it is stored into the named-instvar bits in the * spec field of a class. so i check it against MOO_MAX_NAMED_INSTVARS. */ moo_setsynerr (moo, MOO_SYNERR_NPINSTSIZEINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); if (TOKEN_TYPE(moo) != MOO_IOTOK_RPAREN) { moo_setsynerr (moo, MOO_SYNERR_RPAREN, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); cc->non_pointer_instsize = tmp; } } if (TOKEN_TYPE(moo) != MOO_IOTOK_COMMA) break; /* hopefully ) */ GET_TOKEN (moo); /* get the token after , */ } while (1); } if (TOKEN_TYPE(moo) != MOO_IOTOK_RPAREN) { moo_setsynerr (moo, MOO_SYNERR_RPAREN, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); /* consume the closing ) */ return 0; } static int process_class_superclass (moo_t* moo) { moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; int super_is_nil = 0; int superfqn_is_dotted; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_CLASS); if (TOKEN_TYPE(moo) != MOO_IOTOK_LPAREN) { moo_setsynerrbfmt (moo, MOO_SYNERR_LPAREN, TOKEN_LOC(moo), TOKEN_NAME(moo), "superclass must be specified"); return -1; } /* superclass is specified. new class defintion. * for example, #class Dag(Object) */ GET_TOKEN (moo); /* skip ( and read superclass name */ /* TODO: multiple inheritance */ if (TOKEN_TYPE(moo) == MOO_IOTOK_NIL) { /* #class Dag(nil) */ super_is_nil = 1; } else if (TOKEN_TYPE(moo) != MOO_IOTOK_IDENT && TOKEN_TYPE(moo) != MOO_IOTOK_IDENT_DOTTED) { /* superclass name expected */ moo_setsynerrbfmt (moo, MOO_SYNERR_IDENT, TOKEN_LOC(moo), TOKEN_NAME(moo), "superclass name expected"); return -1; } if (set_superclass_fqn(moo, cc, TOKEN_NAME(moo)) <= -1) return -1; cc->superfqn_loc = moo->c->tok.loc; superfqn_is_dotted = (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT_DOTTED); GET_TOKEN (moo); if (TOKEN_TYPE(moo) != MOO_IOTOK_RPAREN) { moo_setsynerr (moo, MOO_SYNERR_RPAREN, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); /* skip ) and read the next token */ if (super_is_nil) { cc->super_oop = moo->_nil; } else { var_info_t var; if (get_variable_info(moo, &cc->superfqn, &cc->superfqn_loc, superfqn_is_dotted, &var) <= -1) return -1; if (var.type != VAR_GLOBAL) goto unknown_superclass; if (MOO_CLASSOF(moo, var.u.gbl->value) == moo->_class && MOO_OBJ_GET_FLAGS_KERNEL(var.u.gbl->value) != MOO_OBJ_FLAGS_KERNEL_IMMATURE) { /* the value found must be a class and it must not be an incomplete internal class object. * 0(non-kernel object) * 1(incomplete kernel object), * 2(complete kernel object) */ MOO_STORE_OOP (moo, &cc->super_oop, var.u.gbl->value); /* the superclass became known. */ if (((moo_oop_class_t)cc->super_oop)->trsize != moo->_nil && (cc->flags & CLASS_INDEXED) && cc->indexed_type != MOO_OBJ_TYPE_OOP) { /* non-pointer object cannot inherit from a superclass with trailer size set */ moo_setsynerrbfmt (moo, MOO_SYNERR_INHERITBANNED, &cc->fqn_loc, &cc->fqn, "the non-pointer class %.*js cannot inherit from a class set with trailer size", cc->fqn.len, cc->fqn.ptr); return -1; } if (MOO_CLASS_SELFSPEC_FLAGS(MOO_OOP_TO_SMOOI(((moo_oop_class_t)cc->super_oop)->selfspec)) & MOO_CLASS_SELFSPEC_FLAG_FINAL) { /* cannot inherit a #final class */ if (cc->self_oop == MOO_NULL) /* self_oop is not null if it's a predefined kernel class. */ { /* the restriction applies to non-kernel classes only */ moo_setsynerrbfmt (moo, MOO_SYNERR_INHERITBANNED, &cc->fqn_loc, &cc->fqn, "the %.*js class cannot inherit from a final class", cc->fqn.len, cc->fqn.ptr); return -1; } } } else { unknown_superclass: /* there is no object with such a name. or, * the object found with the name is not a class object. or, * the class object found is a internally defined kernel * class object. */ moo_setsynerrbfmt (moo, MOO_SYNERR_NAMEUNDEF, &cc->superfqn_loc, &cc->superfqn, "superclass name undefined"); return -1; } } return 0; } static int process_class_module_import (moo_t* moo) { /* handle the module importing(from) part. * class XXX from 'mod.name' */ moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_CLASS); GET_TOKEN (moo); /* skip 'from' */ if (TOKEN_TYPE(moo) != MOO_IOTOK_STRLIT) { moo_setsynerr (moo, MOO_SYNERR_STRING, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (TOKEN_NAME_LEN(moo) <= 0 || TOKEN_NAME_LEN(moo) > MOO_MOD_NAME_LEN_MAX || moo_find_oochar(TOKEN_NAME_PTR(moo), TOKEN_NAME_LEN(moo), '-') ) { /* check for a bad module name. * also disallow a dash in the name - i like converting * a period to a dash when mapping the module name to an * actual module file. disallowing a dash lowers confusion * when loading a module. */ moo_setsynerrbfmt (moo, MOO_SYNERR_NAMEINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo), "invalid module name"); return -1; } if (set_class_modname(moo, cc, TOKEN_NAME(moo)) <= -1) return -1; cc->modname_loc = *TOKEN_LOC(moo); GET_TOKEN (moo); /* skip the module name and read the next token */ return 0; } static int process_class_interfaces (moo_t* moo) { moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_CLASS); GET_TOKEN (moo); /* skip [ */ if (TOKEN_TYPE(moo) != MOO_IOTOK_IDENT && TOKEN_TYPE(moo) != MOO_IOTOK_IDENT_DOTTED) { moo_setsynerrbfmt (moo, MOO_SYNERR_IDENT, TOKEN_LOC(moo), TOKEN_NAME(moo), "interface name expected"); return -1; } do { var_info_t var; moo_oow_t old_ifce_count; moo_oow_t ifce_index; if (get_variable_info(moo, TOKEN_NAME(moo), TOKEN_LOC(moo), TOKEN_TYPE(moo) == MOO_IOTOK_IDENT_DOTTED, &var) <= -1 || var.type != VAR_GLOBAL || MOO_CLASSOF(moo, var.u.gbl->value) != moo->_interface) { moo_setsynerrbfmt (moo, MOO_SYNERR_NAMEUNDEF, TOKEN_LOC(moo), TOKEN_NAME(moo), "interface name undefined"); return -1; } old_ifce_count = cc->ifces.count; if (add_oop_to_oopbuf_nodup(moo, &cc->ifces, var.u.gbl->value, &ifce_index) <= -1) return -1; if (ifce_index < old_ifce_count) { /* add_oop_to_oopbuf_nodup() returns the index to an existing item * if it's found. the index should be between 0 and the previous count - 1 inclusive. * the index returned will be the previous count if it's added this time */ moo_setsynerrbfmt (moo, MOO_SYNERR_NAMEDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo), "duplicate interface name"); return -1; } #if 0 if (find_word_in_string(&cc->ifce_names, TOKEN_NAME(moo), MOO_NULL) >= 0) { moo_setsynerrbfmt (moo, MOO_SYNERR_NAMEDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo), "duplicate interface name"); return -1; } /* just store the interface name instead of resolved interface object */ if (copy_string_to(moo, TOKEN_NAME(moo), &cc->ifce_names, &cc->ifce_names_capa, 1, ' ') <= -1) return -1; #endif GET_TOKEN (moo); if (TOKEN_TYPE(moo) != MOO_IOTOK_COMMA) break; GET_TOKEN (moo); } while (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT || TOKEN_TYPE(moo) == MOO_IOTOK_IDENT_DOTTED); if (TOKEN_TYPE(moo) != MOO_IOTOK_RBRACK) { moo_setsynerr (moo, MOO_SYNERR_RBRACK, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); /* skip ] and read the next token */ return 0; } struct ciim_t { moo_cunit_class_t* cc; moo_oop_interface_t ifce; moo_oop_class_t _class; int mth_type; }; typedef struct ciim_t ciim_t; static int ciim_on_each_method (moo_t* moo, moo_oop_dic_t dic, moo_oop_association_t ass, void* ctx) { ciim_t* ciim = (ciim_t*)ctx; moo_oocs_t name; moo_oop_method_t mth; name.ptr = MOO_OBJ_GET_CHAR_SLOT(ass->key); name.len = MOO_OBJ_GET_SIZE(ass->key); /* [IMPORTANT] the method lookup logic should be the same as moo_findmethod() in exec.c */ mth = moo_findmethodinclasschain(moo, ciim->_class, ciim->mth_type, &name); if (!mth) { if (MOO_CLASSOF(moo, ass->value) == moo->_method) { moo_oow_t i; moo_oop_method_t im; mth = (moo_oop_method_t)ass->value; for (i = 0; i < ciim->cc->ifce_mths[ciim->mth_type].count; i++) { im = (moo_oop_method_t)ciim->cc->ifce_mths[ciim->mth_type].ptr[i]; if (mth->name == im->name) { /* duplicate interface method name found */ moo_setsynerrbfmt (moo, MOO_SYNERR_MTHNAMEDUPL, MOO_NULL, MOO_NULL, "%.*js defined in multiple interfaces for %.*js - %.*js, %.*js", name.len, name.ptr, MOO_OBJ_GET_SIZE(ciim->_class->name), MOO_OBJ_GET_CHAR_SLOT(ciim->_class->name), MOO_OBJ_GET_SIZE(im->owner->name), MOO_OBJ_GET_CHAR_SLOT(im->owner->name), MOO_OBJ_GET_SIZE(ciim->ifce->name), MOO_OBJ_GET_CHAR_SLOT(ciim->ifce->name) ); return -1; } } if (add_oop_to_oopbuf(moo, &ciim->cc->ifce_mths[ciim->mth_type], (moo_oop_t)mth) <= -1) { const moo_ooch_t* oldmsg = moo_backuperrmsg(moo); moo_seterrbfmt (moo, moo_geterrnum(moo), "unable to take interface method %.*js>>%.*js to %.*js - %js", MOO_OBJ_GET_SIZE(ciim->ifce->name), MOO_OBJ_GET_CHAR_SLOT(ciim->ifce->name), name.len, name.ptr, MOO_OBJ_GET_SIZE(ciim->_class->name), MOO_OBJ_GET_CHAR_SLOT(ciim->_class->name), oldmsg ); return -1; } return 0; } MOO_ASSERT (moo, MOO_CLASSOF(moo, ass->value) == moo->_methsig); moo_setsynerrbfmt (moo, MOO_SYNERR_CLASSNCIFCE, MOO_NULL, MOO_NULL, "%.*js not implementing %.*js>>%.*js", MOO_OBJ_GET_SIZE(ciim->_class->name), MOO_OBJ_GET_CHAR_SLOT(ciim->_class->name), MOO_OBJ_GET_SIZE(ciim->ifce->name), MOO_OBJ_GET_CHAR_SLOT(ciim->ifce->name), name.len, name.ptr ); return -1; } else { /* TODO: * if the method is found in the class chain, also check if the method found * has been taken from the interface. if so, multiple interfaces are defining * the method. it's an error */ } if (MOO_CLASSOF(moo, ass->value) == moo->_methsig) { /* it's a method signature without body */ moo_oop_methsig_t sig; sig = (moo_oop_methsig_t)ass->value; if (MOO_METHOD_GET_PREAMBLE_FLAGS(MOO_OOP_TO_SMOOI(mth->preamble)) != MOO_METHOD_GET_PREAMBLE_FLAGS(MOO_OOP_TO_SMOOI(sig->preamble))) { modifier_conflict: moo_setsynerrbfmt (moo, MOO_SYNERR_CLASSNCIFCE, MOO_NULL, MOO_NULL, "%.*js>>%.*js modifiers conficting with %.*js>>%.*js", MOO_OBJ_GET_SIZE(ciim->_class->name), MOO_OBJ_GET_CHAR_SLOT(ciim->_class->name), name.len, name.ptr, MOO_OBJ_GET_SIZE(ciim->ifce->name), MOO_OBJ_GET_CHAR_SLOT(ciim->ifce->name), name.len, name.ptr ); return -1; } if (mth->tmpr_nargs != sig->tmpr_nargs) /* don't need MOO_OOP_TO_SMOOI */ { param_conflict: moo_setsynerrbfmt (moo, MOO_SYNERR_CLASSNCIFCE, MOO_NULL, MOO_NULL, "%.*js>>%.*js parameters conflicting with %.*js>>%.*js", MOO_OBJ_GET_SIZE(ciim->_class->name), MOO_OBJ_GET_CHAR_SLOT(ciim->_class->name), name.len, name.ptr, MOO_OBJ_GET_SIZE(ciim->ifce->name), MOO_OBJ_GET_CHAR_SLOT(ciim->ifce->name), name.len, name.ptr ); return -1; } } else { /* it is a full interface method */ moo_oop_method_t sig; MOO_ASSERT (moo, MOO_CLASSOF(moo, ass->value) == moo->_method); sig = (moo_oop_method_t)ass->value; if (MOO_METHOD_GET_PREAMBLE_FLAGS(MOO_OOP_TO_SMOOI(mth->preamble)) != MOO_METHOD_GET_PREAMBLE_FLAGS(MOO_OOP_TO_SMOOI(sig->preamble))) { goto modifier_conflict; } if (mth->tmpr_nargs != sig->tmpr_nargs) /* don't need MOO_OOP_TO_SMOOI */ { goto param_conflict; } } return 0; } static int class_implements_interface (moo_t* moo, moo_oop_class_t _class, moo_oop_interface_t ifce) { ciim_t ciim; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_CLASS); ciim.ifce = ifce; ciim._class = _class; ciim.mth_type = MOO_METHOD_INSTANCE; ciim.cc = (moo_cunit_class_t*)moo->c->cunit; if (moo_walkdic(moo, ifce->mthdic[MOO_METHOD_INSTANCE], ciim_on_each_method, &ciim) <= -1) return 0; ciim.ifce = ifce; ciim._class = _class; ciim.mth_type = MOO_METHOD_CLASS; ciim.cc = (moo_cunit_class_t*)moo->c->cunit; if (moo_walkdic(moo, ifce->mthdic[MOO_METHOD_CLASS], ciim_on_each_method, &ciim) <= -1) return 0; return 1; } static int check_class_interface_conformance (moo_t* moo) { moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; moo_oow_t i, j; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_CLASS); for (i = 0; i < cc->ifces.count; i++) { if (!class_implements_interface(moo, cc->self_oop, (moo_oop_interface_t)cc->ifces.ptr[i])) return -1; } MOO_STATIC_ASSERT (MOO_METHOD_INSTANCE == 0 && MOO_METHOD_CLASS == 1); for (j = MOO_METHOD_INSTANCE; j <= MOO_METHOD_CLASS; j++) { for (i = 0; i < cc->ifce_mths[j].count; i++) { moo_oop_method_t mth = (moo_oop_method_t)cc->ifce_mths[j].ptr[i]; if (!moo_putatdic(moo, cc->self_oop->mthdic[j], (moo_oop_t)mth->name, (moo_oop_t)mth)) { const moo_ooch_t* oldmsg = moo_backuperrmsg(moo); mth = (moo_oop_method_t)cc->ifce_mths[j].ptr[i]; /* in case gc has been triggered in moo_putatdic */ moo_seterrbfmt (moo, moo_geterrnum(moo), "unable to put interface method %.*js>>%.*js to %.*js - %js", MOO_OBJ_GET_SIZE(mth->owner->name), MOO_OBJ_GET_CHAR_SLOT(mth->owner->name), MOO_OBJ_GET_SIZE(mth->name), MOO_OBJ_GET_CHAR_SLOT(mth->name), MOO_OBJ_GET_SIZE(cc->self_oop->name), MOO_OBJ_GET_CHAR_SLOT(cc->self_oop->name), oldmsg ); return -1; } } } return 0; } static int __compile_class_definition (moo_t* moo, int class_type) { /* * class-definition := class class-modifier? class-name super-class-spec interface-spec? (class-body | class-module-import) * * class-modifier := "(" (#byte | #character | #word | #pointer)? ")" * super-class-spec := "(" super-class-name ")" * interface-spec := "[" interface-name-list "]" * interface-name-list := interface-name | interface-name "," inteface-name-list * class-body := "{" variable-definition* method-definition* "}" * class-module-import := from "module-name-string" * * TOOD: add | |, |+ |, |* | * variable-definition := (#var | #variable) variable-modifier? variable-list "." * variable-modifier := "(" (#class | #classinst)? ")" * variable-list := identifier* * * method-definition := method method-modifier? method-actual-definition * method-modifier := "(" (#class | #instance)? ")" * method-actual-definition := method-name "{" method-tempraries? method-pragma? method-statements* "}" * * [NOTE] when extending a class, class-module-import and variable-definition are not allowed. */ moo_cunit_class_t* cc = (moo_cunit_class_t*)moo->c->cunit; moo_oop_association_t ass; moo_ioloc_t type_loc; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_CLASS); if (class_type == CLASS_TYPE_NORMAL && TOKEN_TYPE(moo) == MOO_IOTOK_LPAREN) { /* process class modifiers */ MOO_ASSERT (moo, (cc->flags & CLASS_INDEXED) == 0); if (process_class_modifiers(moo, &type_loc) <= -1) return -1; } if (TOKEN_TYPE(moo) != MOO_IOTOK_IDENT && TOKEN_TYPE(moo) != MOO_IOTOK_IDENT_DOTTED) { moo_setsynerrbfmt (moo, MOO_SYNERR_IDENT, TOKEN_LOC(moo), TOKEN_NAME(moo), "class name expected"); return -1; } if (cc->cunit_parent && cc->cunit_parent->cunit_type == MOO_CUNIT_CLASS && TOKEN_TYPE(moo) == MOO_IOTOK_IDENT_DOTTED) { /* the name of a nested class cannot be multi-segmented */ moo_setsynerrbfmt (moo, MOO_SYNERR_IDENT, TOKEN_LOC(moo), TOKEN_NAME(moo), "undotted class name expected"); return -1; } #if 0 if (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT && is_restricted_word(TOKEN_NAME(moo))) { /* wrong class name */ moo_setsynerrbfmt (moo, MOO_SYNERR_NAMEINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo), "invalid class name"); return -1; } #endif /* [NOTE] TOKEN_NAME(moo) doesn't contain the full name if it's nested * inside a class. it is merely a name that appeared in the source * code. * TODO: compose the full name by traversing the namespace chain. */ if (set_class_fqn(moo, cc, TOKEN_NAME(moo)) <= -1) return -1; cc->fqn_loc = moo->c->tok.loc; if (cc->cunit_parent && cc->cunit_parent->cunit_type == MOO_CUNIT_CLASS) { moo_cunit_class_t* c = (moo_cunit_class_t*)cc->cunit_parent; if ((moo_oop_t)c->self_oop->nsdic == moo->_nil) { /* attach a new namespace dictionary to the nsdic field of the class */ if (!attach_nsdic_to_class(moo, c->self_oop)) return -1; } MOO_STORE_OOP (moo, (moo_oop_t*)&cc->ns_oop, (moo_oop_t)c->self_oop->nsdic); /* TODO: if c->nsdic is nil, create one? */ } else if (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT_DOTTED) { if (preprocess_dotted_name(moo, (class_type == CLASS_TYPE_EXTEND? PDN_DONT_ADD_NS: 0), MOO_NULL, &cc->fqn, &cc->fqn_loc, &cc->name, &cc->ns_oop) <= -1) return -1; } else { cc->ns_oop = moo->sysdic; } GET_TOKEN (moo); if (class_type == CLASS_TYPE_EXTEND) { /* extending class */ MOO_ASSERT (moo, cc->flags == 0); MOO_INFO2 (moo, "Extending a class %.*js\n", cc->fqn.len, cc->fqn.ptr); ass = moo_lookupdic_noseterr(moo, (moo_oop_dic_t)cc->ns_oop, &cc->name); if (ass && MOO_CLASSOF(moo, ass->value) == moo->_class && MOO_OBJ_GET_FLAGS_KERNEL(ass->value) != MOO_OBJ_FLAGS_KERNEL_IMMATURE) { /* the value must be a class object. * and it must be either a user-defined(0) or completed kernel built-in(2). * an incomplete kernel built-in class object(1) can not be extended */ cc->self_oop = (moo_oop_class_t)ass->value; } else { /* only an existing class can be extended. */ moo_setsynerrbfmt (moo, MOO_SYNERR_NAMEUNDEF, &cc->fqn_loc, &cc->name, "class name undefined"); return -1; } cc->super_oop = cc->self_oop->superclass; MOO_ASSERT (moo, cc->super_oop == moo->_nil || MOO_CLASSOF(moo, cc->super_oop) == moo->_class); if (TOKEN_TYPE(moo) == MOO_IOTOK_LBRACK) { if (process_class_interfaces(moo) <= -1) return -1; } } else { MOO_INFO2 (moo, "Defining a class %.*js\n", cc->fqn.len, cc->fqn.ptr); ass = moo_lookupdic_noseterr(moo, (moo_oop_dic_t)cc->ns_oop, &cc->name); if (ass) { if (MOO_CLASSOF(moo, ass->value) != moo->_class || MOO_OBJ_GET_FLAGS_KERNEL(ass->value) == MOO_OBJ_FLAGS_KERNEL_MATURE) { /* the object found with the name is not a class object * or the the class object found is a fully defined kernel * class object */ moo_setsynerrbfmt (moo, MOO_SYNERR_NAMEDUPL, &cc->fqn_loc, &cc->name, "duplicate class name"); return -1; } cc->self_oop = (moo_oop_class_t)ass->value; } else { /* no class of such a name is found. it's a new definition, * which is normal for most new classes. */ MOO_ASSERT (moo, cc->self_oop == MOO_NULL); } if (process_class_superclass(moo) <= -1) return -1; if (TOKEN_TYPE(moo) == MOO_IOTOK_LBRACK) { if (process_class_interfaces(moo) <= -1) return -1; } if (is_token_word (moo, VOCA_FROM)) { if (process_class_module_import(moo) <= -1) return -1; } } cc->in_class_body = 1; if (TOKEN_TYPE(moo) != MOO_IOTOK_LBRACE) { moo_setsynerr (moo, MOO_SYNERR_LBRACE, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } MOO_ASSERT (moo, cc->super_oop != MOO_NULL); if (cc->super_oop != moo->_nil) { /* adjust the instance variable count and the class instance variable * count to include that of a superclass */ moo_oop_class_t c; moo_oow_t spec, self_spec; c = (moo_oop_class_t)cc->super_oop; spec = MOO_OOP_TO_SMOOI(c->spec); self_spec = MOO_OOP_TO_SMOOI(c->selfspec); /* [NOTE] class variables are not inherited. * so no data about them are not transferred over */ if ((cc->flags & CLASS_INDEXED) && cc->indexed_type != MOO_OBJ_TYPE_OOP) { /* the class defined is a non-pointer object. */ if (MOO_CLASS_SPEC_INDEXED_TYPE(spec) == MOO_OBJ_TYPE_OOP && MOO_CLASS_SPEC_NAMED_INSTVARS(spec) > 0) { /* a non-pointer object cannot inherit from a pointer object with instance variables */ moo_setsynerrbfmt (moo, MOO_SYNERR_INHERITBANNED, &cc->fqn_loc, &cc->fqn, "the non-pointer class %.*js cannot inherit from a pointer class defined with instance variables", cc->fqn.len, cc->fqn.ptr); return -1; } /* [NOTE] I don't mandate that the parent and the child be of the same type. * Say, for a parent class(#byte(4)), a child can be defined to be * class(#word(4)). */ if (cc->non_pointer_instsize < MOO_CLASS_SPEC_NAMED_INSTVARS(spec)) { moo_setsynerrbfmt (moo, MOO_SYNERR_NPINSTSIZEINVAL, &type_loc, MOO_NULL, "the instance size(%zu) for the non-pointer class %.*js must not be less than the size(%zu) defined in the superclass ", cc->non_pointer_instsize, cc->fqn.len, cc->fqn.ptr, (moo_oow_t)MOO_CLASS_SPEC_NAMED_INSTVARS(spec)); return -1; } } else { /* the class defined is a pointer object or a variable-pointer object */ MOO_ASSERT (moo, cc->non_pointer_instsize == 0); /* no such thing as class(#pointer(N)). so it must be 0 */ cc->var[VAR_INSTANCE].total_count = MOO_CLASS_SPEC_NAMED_INSTVARS(spec); } cc->var[VAR_CLASSINST].total_count = MOO_CLASS_SELFSPEC_CLASSINSTVARS(self_spec); } GET_TOKEN (moo); if (moo->dbgi) { moo_oow_t file_offset; const moo_ooch_t* file_name; file_name = cc->fqn_loc.file; if (!file_name) file_name = &_nul; if (moo_addfiletodbgi(moo, file_name, &file_offset) <= -1) { /* TODO: warning */ file_offset = 0; } else if (file_offset > MOO_SMOOI_MAX) { /* TODO: warning */ file_offset = 0; } if (moo_addclasstodbgi(moo, cc->fqn.ptr, file_offset, cc->fqn_loc.line, &cc->dbgi_class_offset) <= -1) { /* TODO: warning. no debug information about this method will be available */ } /* TODO: store source file offset and class offset in the class object only for NORMAL. make_defined_class is a good candidate to do this in. */ } if (class_type == CLASS_TYPE_EXTEND) { moo_oop_char_t pds; /* when a class is extended, a new variable cannot be added */ if (is_token_word(moo, VOCA_VAR) || is_token_word(moo, VOCA_VARIABLE) || is_token_binary_selector(moo, VOCA_VBAR) || is_token_binary_selector(moo, VOCA_VBAR_PLUS) || is_token_binary_selector(moo, VOCA_VBAR_ASTER)) { moo_setsynerr (moo, MOO_SYNERR_VARDCLBANNED, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } /* load the pooldic definition from the existing class object */ pds = cc->self_oop->pooldics; if ((moo_oop_t)pds != moo->_nil) { moo_ooch_t* ptr, * end; MOO_ASSERT (moo, MOO_CLASSOF(moo, pds) == moo->_string); ptr = MOO_OBJ_GET_CHAR_SLOT(pds); end = ptr + MOO_OBJ_GET_SIZE(pds); /* this loop handles the pooldic string as if it's a pooldic import. * see compile_class_level_variables() for mostly identical code except token handling */ do { moo_oocs_t last, tok; moo_ioloc_t loc; int dotted = 0; moo_oop_nsdic_t ns_oop; while (ptr < end && is_spacechar(*ptr)) ptr++; if (ptr >= end) break; MOO_MEMSET (&loc, 0, MOO_SIZEOF(loc)); /* fake location */ tok.ptr = ptr; while (ptr < end && !is_spacechar(*ptr)) { if (*ptr == '.') dotted = 1; ptr++; } tok.len = ptr - tok.ptr; MOO_ASSERT (moo, tok.len > 0); if (dotted) { if (preprocess_dotted_name(moo, 0, MOO_NULL, &tok, &loc, &last, &ns_oop) <= -1) return -1; } else { last = tok; /* it falls back to the name space of the class */ ns_oop = cc->ns_oop; } if (import_pooldic(moo, cc, ns_oop, &last, &tok, &loc) <= -1) return -1; } while (1); } /* TODO: load constants here? */ } else { /* a new class including an internally defined class object */ do { if (is_token_binary_selector(moo, VOCA_VBAR)) { /* instance variables | a b c | */ GET_TOKEN (moo); if (compile_class_level_variables_vbar(moo, VAR_INSTANCE) <= -1) return -1; } else if (is_token_binary_selector(moo, VOCA_VBAR_ASTER)) { /* class instance variables |* a b c | */ GET_TOKEN (moo); if (compile_class_level_variables_vbar(moo, VAR_CLASSINST) <= -1) return -1; } else if (is_token_binary_selector(moo, VOCA_VBAR_PLUS)) { /* class variables |+ a b c | */ GET_TOKEN (moo); if (compile_class_level_variables_vbar(moo, VAR_CLASS) <= -1) return -1; } else if (is_token_word(moo, VOCA_VAR) || is_token_word(moo, VOCA_VARIABLE)) { /* variable declaration */ GET_TOKEN (moo); if (compile_class_level_variables(moo) <= -1) return -1; } else if (is_token_word(moo, VOCA_IMPORT)) { /* import declaration */ GET_TOKEN (moo); if (compile_class_level_imports(moo) <= -1) return -1; } else break; } while (1); if (make_defined_class(moo) <= -1) return -1; if (cc->modname.len > 0) { MOO_ASSERT (moo, MOO_CLASSOF(moo, cc->self_oop->modname) == moo->_symbol); /* [NOTE] * even if the modname field is set in the class object itself * by make_define_class(), i pass the module name in the compiler * memory(not part of the object memory) to moo_importmod(). * no big overhead as it's already available. but Accessing * this extra module name, i'm free from GC headache */ if (moo_importmod(moo, cc->self_oop, cc->modname.ptr, cc->modname.len) <= -1) { const moo_ooch_t* oldmsg = moo_backuperrmsg(moo); moo_setsynerrbfmt (moo, MOO_SYNERR_MODIMPFAIL, &cc->modname_loc, &cc->modname, "unable to import %.*js - %js", cc->modname.len, cc->modname.ptr, oldmsg); return -1; } } if (cc->self_oop->trsize == moo->_nil && cc->self_oop->superclass != moo->_nil) { /* the trailer size has not been set by the module importer. * if the superclass has the trailer size set, this class must * inherit so that the inherited methods work well when they * access the trailer space */ cc->self_oop->trsize = ((moo_oop_class_t)cc->self_oop->superclass)->trsize; cc->self_oop->trgc = ((moo_oop_class_t)cc->self_oop->superclass)->trgc; } } do { if (is_token_word(moo, VOCA_METHOD)) { /* method definition in class definition */ GET_TOKEN (moo); if (compile_method_definition(moo) <= -1) return -1; } else if (is_token_word(moo, VOCA_POOLDIC)) { /* pooldic definition nested inside class definition */ GET_TOKEN (moo); if (compile_pooldic_definition(moo) <= -1) return -1; } else if (is_token_word(moo, VOCA_CLASS)) { /* class definition nested inside another class definition */ GET_TOKEN (moo); if (compile_class_definition(moo, CLASS_TYPE_NORMAL) <= -1) return -1; } else if (is_token_word(moo, VOCA_EXTEND)) { /* class extension nested inside another class definition */ GET_TOKEN (moo); if (compile_class_definition(moo, CLASS_TYPE_EXTEND) <= -1) return -1; } else if (is_token_word(moo, VOCA_INTERFACE)) { /* interface nested inside another class definition */ GET_TOKEN (moo); if (compile_interface_definition(moo) <= -1) return -1; } else break; } while (1); if (TOKEN_TYPE(moo) != MOO_IOTOK_RBRACE) { moo_setsynerr (moo, MOO_SYNERR_RBRACE, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (cc->ifces.count > 0) { /* interface has been specified. */ if (check_class_interface_conformance(moo) <= -1) return -1; } if (cc->pdimp.dcl.len > 0) { moo_oop_t tmp; tmp = moo_makestring(moo, cc->pdimp.dcl.ptr, cc->pdimp.dcl.len); if (!tmp) return -1; MOO_STORE_OOP (moo, (moo_oop_t*)&cc->self_oop->pooldics, tmp); } GET_TOKEN (moo); return 0; } static int compile_class_definition (moo_t* moo, int class_type) { int n; if (!push_cunit(moo, MOO_CUNIT_CLASS)) return -1; MOO_ASSERT (moo, moo->c->balit.count == 0); MOO_ASSERT (moo, moo->c->arlit.count == 0); n = __compile_class_definition (moo, class_type); MOO_ASSERT (moo, moo->c->balit.count == 0); MOO_ASSERT (moo, moo->c->arlit.count == 0); pop_cunit (moo); return n; } static int __compile_method_signature (moo_t* moo) { moo_cunit_interface_t* ifce = (moo_cunit_interface_t*)moo->c->cunit; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_INTERFACE); if (TOKEN_TYPE(moo) == MOO_IOTOK_LPAREN) { /* process method modifiers */ if (process_method_modifiers(moo) <= -1) return -1; } if (compile_method_name(moo) <= -1) return -1; if (TOKEN_TYPE(moo) == MOO_IOTOK_LBRACE) { /* you can inlcude method body optionally */ GET_TOKEN (moo); if (compile_method_temporaries(moo) <= -1 || compile_method_pragma(moo) <= -1 || /* TODO: disallow pragmas... */ compile_method_statements(moo) <= -1) return -1; if (TOKEN_TYPE(moo) != MOO_IOTOK_RBRACE) { /* } expected */ moo_setsynerr (moo, MOO_SYNERR_RBRACE, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } /* end of method has been reached */ if (resolve_goto_labels(moo) <= -1) return -1; GET_TOKEN (moo); /* add a compiled method to the method dictionary */ if (add_compiled_method_to_interface(moo) <= -1) return -1; return 0; } else if (TOKEN_TYPE(moo) == MOO_IOTOK_PERIOD) { if (add_method_signature(moo) <= -1) return -1; GET_TOKEN (moo); return 0; } /* . or { expected */ moo_setsynerrbfmt (moo, MOO_SYNERR_PERIOD, TOKEN_LOC(moo), TOKEN_NAME(moo), "period or left brace expected"); return -1; } static int compile_method_signature (moo_t* moo) { moo_cunit_interface_t* ifce = (moo_cunit_interface_t*)moo->c->cunit; int n; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_INTERFACE); MOO_ASSERT (moo, moo->c->balit.count == 0); MOO_ASSERT (moo, moo->c->arlit.count == 0); ifce->mth.active = 1; reset_method_data (moo, &ifce->mth); n = __compile_method_signature(moo); ifce->mth.active = 0; return n; } static int make_defined_interface (moo_t* moo) { moo_cunit_interface_t* ifce = (moo_cunit_interface_t*)moo->c->cunit; moo_oop_t tmp; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_INTERFACE); tmp = moo_instantiate(moo, moo->_interface, MOO_NULL, 0); if (!tmp) return -1; ifce->self_oop = (moo_oop_interface_t)tmp; tmp = moo_makesymbol(moo, ifce->name.ptr, ifce->name.len); if (!tmp) return -1; MOO_STORE_OOP (moo, (moo_oop_t*)&ifce->self_oop->name, tmp); tmp = (moo_oop_t)moo_makedic(moo, moo->_method_dictionary, INSTANCE_METHOD_DICTIONARY_SIZE); if (!tmp) return -1; MOO_STORE_OOP (moo, (moo_oop_t*)&ifce->self_oop->mthdic[MOO_METHOD_INSTANCE], tmp); tmp = (moo_oop_t)moo_makedic(moo, moo->_method_dictionary, CLASS_METHOD_DICTIONARY_SIZE); if (!tmp) return -1; MOO_STORE_OOP (moo, (moo_oop_t*)&ifce->self_oop->mthdic[MOO_METHOD_CLASS], tmp); if (!moo_putatdic(moo, (moo_oop_dic_t)ifce->ns_oop, (moo_oop_t)ifce->self_oop->name, (moo_oop_t)ifce->self_oop)) return -1; MOO_STORE_OOP (moo, (moo_oop_t*)&ifce->self_oop->nsup, (moo_oop_t)ifce->ns_oop); return 0; } static int __compile_interface_definition (moo_t* moo) { moo_cunit_interface_t* ifce = (moo_cunit_interface_t*)moo->c->cunit; moo_oop_association_t ass; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_INTERFACE); if (TOKEN_TYPE(moo) != MOO_IOTOK_IDENT && TOKEN_TYPE(moo) != MOO_IOTOK_IDENT_DOTTED) { moo_setsynerrbfmt (moo, MOO_SYNERR_IDENT, TOKEN_LOC(moo), TOKEN_NAME(moo), "interface name expected"); return -1; } if (ifce->cunit_parent && ifce->cunit_parent->cunit_type == MOO_CUNIT_CLASS && TOKEN_TYPE(moo) == MOO_IOTOK_IDENT_DOTTED) { /* the name of a nested class cannot be multi-segmented */ moo_setsynerrbfmt (moo, MOO_SYNERR_IDENT, TOKEN_LOC(moo), TOKEN_NAME(moo), "undotted interface name expected"); return -1; } #if 0 if (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT && is_restricted_word(TOKEN_NAME(moo))) { /* wrong class name */ moo_setsynerr (moo, MOO_SYNERR_NAMEINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo), "invalid interface name"); return -1; } #endif /* [NOTE] TOKEN_NAME(moo) doesn't contain the full name if it's nested * inside a class. it is merely a name that appeared in the source * code. * TODO: compose the full name by traversing the namespace chain. */ if (set_interface_fqn(moo, ifce, TOKEN_NAME(moo)) <= -1) return -1; ifce->fqn_loc = moo->c->tok.loc; if (ifce->cunit_parent && ifce->cunit_parent->cunit_type == MOO_CUNIT_CLASS) { /* nested inside a class */ moo_cunit_class_t* c = (moo_cunit_class_t*)ifce->cunit_parent; if ((moo_oop_t)c->self_oop->nsdic == moo->_nil) { /* attach a new namespace dictionary to the nsdic field of the class */ if (!attach_nsdic_to_class(moo, c->self_oop)) return -1; } ifce->ns_oop = c->self_oop->nsdic; /* TODO: if c->nsdic is nil, create one? */ } else if (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT_DOTTED) { if (preprocess_dotted_name(moo, 0, MOO_NULL, &ifce->fqn, &ifce->fqn_loc, &ifce->name, &ifce->ns_oop) <= -1) return -1; } else { ifce->ns_oop = moo->sysdic; } GET_TOKEN (moo); MOO_INFO2 (moo, "Defining an interface %.*js\n", ifce->fqn.len, ifce->fqn.ptr); ass = moo_lookupdic_noseterr(moo, (moo_oop_dic_t)ifce->ns_oop, &ifce->name); if (ass) { /* The interface name already exists. An interface cannot be defined with an existing name */ moo_setsynerrbfmt (moo, MOO_SYNERR_NAMEDUPL, &ifce->fqn_loc, &ifce->name, "duplicate interface name"); return -1; } if (TOKEN_TYPE(moo) != MOO_IOTOK_LBRACE) { moo_setsynerr (moo, MOO_SYNERR_LBRACE, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); /* an interface cannot have variables */ if (is_token_word(moo, VOCA_VAR) || is_token_word(moo, VOCA_VARIABLE) || is_token_binary_selector(moo, VOCA_VBAR) || is_token_binary_selector(moo, VOCA_VBAR_PLUS) || is_token_binary_selector(moo, VOCA_VBAR_ASTER)) { moo_setsynerr (moo, MOO_SYNERR_VARDCLBANNED, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (moo->dbgi) { moo_oow_t file_offset; const moo_ooch_t* file_name; file_name = ifce->fqn_loc.file; if (!file_name) file_name = &_nul; if (moo_addfiletodbgi(moo, file_name, &file_offset) <= -1) { /* TODO: warning */ file_offset = 0; } else if (file_offset > MOO_SMOOI_MAX) { /* TODO: warning */ file_offset = 0; } /* call moo_addclasstobdgi() for both an interface or a class */ if (moo_addclasstodbgi(moo, ifce->fqn.ptr, file_offset, ifce->fqn_loc.line, &ifce->dbgi_interface_offset) <= -1) { /* TODO: warning. no debug information about this method will be available */ } } if (make_defined_interface(moo) <= -1) return -1; do { if (is_token_word(moo, VOCA_METHOD)) { /* method definition in class definition */ GET_TOKEN (moo); if (compile_method_signature(moo) <= -1) return -1; } else break; } while (1); if (TOKEN_TYPE(moo) != MOO_IOTOK_RBRACE) { moo_setsynerr (moo, MOO_SYNERR_RBRACE, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); return 0; } static int compile_interface_definition (moo_t* moo) { int n; if (!push_cunit(moo, MOO_CUNIT_INTERFACE)) return -1; MOO_ASSERT (moo, moo->c->balit.count == 0); MOO_ASSERT (moo, moo->c->arlit.count == 0); n = __compile_interface_definition (moo); MOO_ASSERT (moo, moo->c->balit.count == 0); MOO_ASSERT (moo, moo->c->arlit.count == 0); pop_cunit (moo); return n; } static moo_oop_t token_to_literal (moo_t* moo, int rdonly) { switch (TOKEN_TYPE(moo)) { case MOO_IOTOK_NIL: return moo->_nil; case MOO_IOTOK_TRUE: return moo->_true; case MOO_IOTOK_FALSE: return moo->_false; case MOO_IOTOK_ERRLIT: return string_to_error(moo, TOKEN_NAME(moo), TOKEN_LOC(moo)); case MOO_IOTOK_SMPTRLIT: return string_to_ptr(moo, TOKEN_NAME(moo), TOKEN_LOC(moo)); case MOO_IOTOK_CHARLIT: MOO_ASSERT (moo, TOKEN_NAME_LEN(moo) == 1); return MOO_CHAR_TO_OOP(TOKEN_NAME_PTR(moo)[0]); case MOO_IOTOK_INTLIT: case MOO_IOTOK_RADINTLIT: { moo_oop_t lit; lit = string_to_int(moo, TOKEN_NAME(moo), TOKEN_TYPE(moo) == MOO_IOTOK_RADINTLIT); if (rdonly && lit && MOO_OOP_IS_POINTER(lit)) MOO_OBJ_SET_FLAGS_RDONLY (lit, 1); return lit; } case MOO_IOTOK_FPDECLIT: case MOO_IOTOK_SCALEDFPDECLIT: { moo_oop_t lit; lit = string_to_fpdec(moo, TOKEN_NAME(moo), TOKEN_TYPE(moo) == MOO_IOTOK_SCALEDFPDECLIT); if (rdonly && lit && MOO_OOP_IS_POINTER(lit)) MOO_OBJ_SET_FLAGS_RDONLY (lit, 1); return lit; } case MOO_IOTOK_SYMLIT: /* a symbol is always set with RDONLY. no additional check is needed here */ return moo_makesymbol(moo, TOKEN_NAME_PTR(moo) + 1, TOKEN_NAME_LEN(moo) - 1); case MOO_IOTOK_STRLIT: { moo_oop_t lit; lit = moo_instantiate(moo, moo->_string, TOKEN_NAME_PTR(moo), TOKEN_NAME_LEN(moo)); if (rdonly && lit) { MOO_ASSERT (moo, MOO_OOP_IS_POINTER(lit)); MOO_OBJ_SET_FLAGS_RDONLY (lit, 1); } return lit; } case MOO_IOTOK_BYTEARRAYLIT: { moo_oop_t lit; moo_oow_t i; lit = moo_instantiate(moo, moo->_byte_array, MOO_NULL, TOKEN_NAME_LEN(moo)); if (lit) { for (i = 0; i < MOO_OBJ_GET_SIZE(lit); i++) MOO_OBJ_SET_BYTE_VAL(lit, i, TOKEN_NAME_PTR(moo)[i]); if (rdonly) { MOO_ASSERT (moo, MOO_OOP_IS_POINTER(lit)); MOO_OBJ_SET_FLAGS_RDONLY (lit, 1); } } return lit; } case MOO_IOTOK_IDENT: case MOO_IOTOK_IDENT_DOTTED: { var_info_t var; if (get_variable_info(moo, TOKEN_NAME(moo), TOKEN_LOC(moo), TOKEN_TYPE(moo) == MOO_IOTOK_IDENT_DOTTED, &var) <= -1) return MOO_NULL; if (var.type == VAR_GLOBAL) { return var.u.gbl->value; } else if (var.type == VAR_LITERAL) { return var.u.lit; } else { moo_setsynerr (moo, MOO_SYNERR_VARINACC, TOKEN_LOC(moo), TOKEN_NAME(moo)); return MOO_NULL; } /* [NOTE] i don't mark RDONLY on a value resolved via an identifier */ } case MOO_IOTOK_HASHBRACK: /* #[ - byte array literal parenthesis */ { moo_oop_t lit; if (read_byte_array_literal(moo, rdonly, &lit) <= -1) return MOO_NULL; return lit; } case MOO_IOTOK_HASHPAREN: /* #( - array literal parenthesis */ { moo_oop_t lit; if (read_array_literal(moo, rdonly, &lit) <= -1) return MOO_NULL; return lit; } default: moo_setsynerr (moo, MOO_SYNERR_LITERAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return MOO_NULL; } } static moo_oop_t find_element_in_compiling_pooldic (moo_t* moo, const moo_oocs_t* name) { moo_oow_t i; moo_oop_char_t s; moo_cunit_pooldic_t* pd; MOO_ASSERT (moo, moo->c->cunit->cunit_type == MOO_CUNIT_POOLDIC); pd = (moo_cunit_pooldic_t*)moo->c->cunit; for (i = pd->start; i < pd->end; i += 2) { s = (moo_oop_char_t)moo->c->arlit.ptr[i]; MOO_ASSERT (moo, MOO_CLASSOF(moo,s) == moo->_symbol); if (MOO_OBJ_GET_SIZE(s) == name->len && moo_equal_oochars(name->ptr, MOO_OBJ_GET_CHAR_SLOT(s), name->len)) { return moo->c->arlit.ptr[i + 1]; } } moo_seterrnum (moo, MOO_ENOENT); return MOO_NULL; } static int __compile_pooldic_definition (moo_t* moo) { moo_oop_t tmp; moo_ooi_t tally; moo_oow_t i; moo_oow_t saved_arlit_count; moo_cunit_pooldic_t* pd; pd = (moo_cunit_pooldic_t*)moo->c->cunit; saved_arlit_count = moo->c->arlit.count; if (TOKEN_TYPE(moo) != MOO_IOTOK_IDENT && TOKEN_TYPE(moo) != MOO_IOTOK_IDENT_DOTTED) { moo_setsynerrbfmt (moo, MOO_SYNERR_IDENT, TOKEN_LOC(moo), TOKEN_NAME(moo), "pooldic name expected"); goto oops; } if (pd->cunit_parent && pd->cunit_parent->cunit_type == MOO_CUNIT_CLASS && TOKEN_TYPE(moo) == MOO_IOTOK_IDENT_DOTTED) { /* the pool dictionary nested inside a class cannot be multi-segmented */ moo_setsynerrbfmt (moo, MOO_SYNERR_IDENT, TOKEN_LOC(moo), TOKEN_NAME(moo), "undotted pooldic name expected"); goto oops; } #if 0 if (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT && is_restricted_word(TOKEN_NAME(moo))) { /* wrong pooldic name */ moo_setsynerr (moo, MOO_SYNERR_NAMEINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo), "invalid pooldic name"); return -1; } #endif /* [NOTE] TOKEN_NAME(moo) doesn't contain the full name if it's nested * inside a class. it is merely a name that appeared in the source * code. * TODO: compose the full name by traversing the namespace chain. */ if (set_pooldic_fqn(moo, pd, TOKEN_NAME(moo)) <= -1) goto oops; pd->fqn_loc = moo->c->tok.loc; if (pd->cunit_parent && pd->cunit_parent->cunit_type == MOO_CUNIT_CLASS) { /* nested inside a class */ moo_cunit_class_t* c = (moo_cunit_class_t*)pd->cunit_parent; if ((moo_oop_t)c->self_oop->nsdic == moo->_nil) { /* attach a new namespace dictionary to the nsdic field of the class */ if (!attach_nsdic_to_class(moo, c->self_oop)) return -1; } MOO_STORE_OOP (moo, (moo_oop_t*)&pd->ns_oop, (moo_oop_t)c->self_oop->nsdic); /* TODO: if c->nsdic is nil, create one? */ } else if (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT_DOTTED) { if (preprocess_dotted_name(moo, 0, MOO_NULL, &pd->fqn, &pd->fqn_loc, &pd->name, &pd->ns_oop) <= -1) goto oops; } else { MOO_STORE_OOP (moo, (moo_oop_t*)&pd->ns_oop, (moo_oop_t)moo->sysdic); } if (moo_lookupdic_noseterr(moo, (moo_oop_dic_t)pd->ns_oop, &pd->name)) { /* a conflicting entry has been found */ moo_setsynerrbfmt (moo, MOO_SYNERR_NAMEDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo), "duplicate pooldic name"); goto oops; } GET_TOKEN (moo); if (TOKEN_TYPE(moo) != MOO_IOTOK_LBRACE) { moo_setsynerr (moo, MOO_SYNERR_LBRACE, TOKEN_LOC(moo), TOKEN_NAME(moo)); goto oops; } MOO_INFO2 (moo, "Defining a pool dictionary %.*js\n", pd->fqn.len, pd->fqn.ptr); GET_TOKEN (moo); pd->start = moo->c->arlit.count; pd->end = moo->c->arlit.count; while (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT) { tmp = moo_makesymbol(moo, TOKEN_NAME_PTR(moo), TOKEN_NAME_LEN(moo)); if (!tmp) goto oops; if (find_in_array_literal_buffer(moo, saved_arlit_count, 2, tmp, MOO_NULL) >= 0) { moo_setsynerr (moo, MOO_SYNERR_NAMEDUPL, TOKEN_LOC(moo), TOKEN_NAME(moo)); goto oops; } if (add_to_array_literal_buffer(moo, tmp) <= -1) goto oops; GET_TOKEN (moo); if (TOKEN_TYPE(moo) == MOO_IOTOK_IDENT && is_token_word(moo, VOCA_FROM)) { moo_pvbase_t* pvbase; moo_cunit_class_t* pcc; moo_oow_t savedlen; /* * pooldic X { * O_RDONLY := 0, * O_WRONLY from "O_WRONLY" * } */ if (!pd->cunit_parent || pd->cunit_parent->cunit_type != MOO_CUNIT_CLASS) { moo_setsynerrbfmt (moo, MOO_SYNERR_PVIMPBANNED, TOKEN_LOC(moo), TOKEN_NAME(moo), "primitive value from module not allowed in non-nested pooldic"); goto oops; } pcc = (moo_cunit_class_t*)pd->cunit_parent; if (pcc->modname.len <= 0) { moo_setsynerrbfmt (moo, MOO_SYNERR_PVIMPBANNED, TOKEN_LOC(moo), TOKEN_NAME(moo), "primitive value from module not allowed without module from class"); goto oops; } GET_TOKEN (moo); if (TOKEN_TYPE(moo) != MOO_IOTOK_STRLIT) { moo_setsynerr (moo, MOO_SYNERR_PVIDINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); goto oops; } if (moo_rfind_oochar(TOKEN_NAME_PTR(moo), TOKEN_NAME_LEN(moo), '.')) { moo_setsynerrbfmt (moo, MOO_SYNERR_PVIDINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo), "primitive value identifier disallowed to have period"); goto oops; } /* append the primitive value name to the module name of the parent class. * it is restored to the saved length after module query for primitive value */ savedlen = pcc->modname.len; if (copy_string_to (moo, TOKEN_NAME(moo), &pcc->modname, &pcc->modname_capa, 1, '.') <= -1) { pcc->modname.len = savedlen; goto oops; } pvbase = moo_querymodpv(moo, pcc->modname.ptr, pcc->modname.len, MOO_NULL); pcc->modname.len = savedlen; if (!pvbase) { const moo_ooch_t* oldmsg = moo_backuperrmsg(moo); moo_setsynerrbfmt (moo, MOO_SYNERR_PVIDINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo), "wrong primitive value identifier - %js", oldmsg); goto oops; } switch (pvbase->type) { case MOO_PV_OOI: { tmp = moo_ooitoint(moo, (moo_ooi_t)pvbase->value); break; } case MOO_PV_OOW: { tmp = moo_oowtoint(moo, (moo_oow_t)pvbase->value); break; } /* TODO: support more types... MOO_PV_OOI_BCSTR, MOO_PV_OOI_UCSTR, MOO_PV_FPDEC_BCSTR, MOO_PV_BCSTR, MOO_PV_UCSTR, etc */ default: moo_setsynerrbfmt (moo, MOO_SYNERR_STRING, TOKEN_LOC(moo), TOKEN_NAME(moo), "unsupported primitive value type - %d", pvbase->type); goto oops; } if (!tmp) goto oops; } else if (TOKEN_TYPE(moo) == MOO_IOTOK_ASSIGN) { GET_TOKEN (moo); tmp = token_to_literal(moo, 1); if (!tmp) goto oops; } else { moo_setsynerr (moo, MOO_SYNERR_ASSIGN, TOKEN_LOC(moo), TOKEN_NAME(moo)); goto oops; } /* for this definition, #pooldic MyPoolDic { a := 10. b := 20 }, * arlit_buffer contains (#a 10 #b 20) when the 'while' loop is over. */ if (add_to_array_literal_buffer(moo, tmp) <= -1) goto oops; pd->end = moo->c->arlit.count; GET_TOKEN (moo); if (TOKEN_TYPE(moo) == MOO_IOTOK_RBRACE) goto done; if (TOKEN_TYPE(moo) != MOO_IOTOK_COMMA) { moo_setsynerrbfmt (moo, MOO_SYNERR_COMMA, TOKEN_LOC(moo), TOKEN_NAME(moo), ", expected to separate pooldic items"); goto oops; } GET_TOKEN (moo); /* comment out the following check to allow the last item * to be followed by a comma. what is better? */ if (TOKEN_TYPE(moo) != MOO_IOTOK_IDENT) { moo_setsynerrbfmt (moo, MOO_SYNERR_IDENT, TOKEN_LOC(moo), TOKEN_NAME(moo), "identifier expected after ,"); goto oops; } } if (TOKEN_TYPE(moo) != MOO_IOTOK_RBRACE) { moo_setsynerr (moo, MOO_SYNERR_RBRACE, TOKEN_LOC(moo), TOKEN_NAME(moo)); goto oops; } done: GET_TOKEN (moo); tally = (moo->c->arlit.count - saved_arlit_count) / 2; /*TODO: tally and arlit_count range check */ /*if (!MOO_IN_SMOOI_RANGE(tally)) ERROR??*/ tmp = (moo_oop_t)moo_makedic(moo, moo->_pool_dictionary, MOO_ALIGN(tally + 10, POOL_DICTIONARY_SIZE_ALIGN)); if (!tmp) goto oops; MOO_STORE_OOP (moo, (moo_oop_t*)&pd->pd_oop, tmp); for (i = saved_arlit_count; i < moo->c->arlit.count; i += 2) { if (!moo_putatdic(moo, pd->pd_oop, moo->c->arlit.ptr[i], moo->c->arlit.ptr[i + 1])) goto oops; } /* eveything seems ok. register the pool dictionary to the main * system dictionary or to the name space it belongs to */ tmp = moo_makesymbol(moo, pd->name.ptr, pd->name.len); if (!tmp || !moo_putatdic(moo, (moo_oop_dic_t)pd->ns_oop, tmp, (moo_oop_t)pd->pd_oop)) goto oops; if (pd->cunit_parent && pd->cunit_parent->cunit_type == MOO_CUNIT_CLASS) { /* a pool dictionary nested inside a class is auto-imported. * change pd->fqn to form a fully qualified name for importing. * the full name is required for restoration upon 'extend'.*/ moo_cunit_class_t* cc = (moo_cunit_class_t*)pd->cunit_parent; static moo_ooch_t str_dot[] = { '.' }; static const moo_oocs_t oocs_dot = { str_dot, 1 }; if (prefix_pooldic_fqn (moo, pd, &oocs_dot) <= -1 || prefix_pooldic_fqn (moo, pd, &cc->fqn) <= -1 || import_pooldic(moo, cc, pd->ns_oop, &pd->name, &pd->fqn, &pd->fqn_loc) <= -1) goto oops; } moo->c->arlit.count = saved_arlit_count; return 0; oops: moo->c->arlit.count = saved_arlit_count; return -1; } static int compile_pooldic_definition (moo_t* moo) { int n; if (!push_cunit(moo, MOO_CUNIT_POOLDIC)) return -1; MOO_ASSERT (moo, moo->c->balit.count == 0); MOO_ASSERT (moo, moo->c->arlit.count == 0); n = __compile_pooldic_definition(moo); MOO_ASSERT (moo, moo->c->balit.count == 0); MOO_ASSERT (moo, moo->c->arlit.count == 0); pop_cunit (moo); return n; } static int compile_pragma_definition (moo_t* moo) { do { GET_TOKEN (moo); if (TOKEN_TYPE(moo) != MOO_IOTOK_IDENT) { moo_setsynerr (moo, MOO_SYNERR_IDENT, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } #if 0 /* TODO: pragma push */ if (is_token_word(moo, VOCA_PUSH)) { /* #pragma push() - saves the pragma flags and keep the existing flags */ /* #pragma push(reset) - saves the pragma flags and reset the flags to 0 */ } else if (is_token_word(moo, VOCA_POP)) { /* #pragma pop() */ } else #endif if (is_token_word(moo, VOCA_QC)) { /* #pragma qc(on). #pragma qc(off) - enable/disable double-quoted comments */ GET_TOKEN(moo); if (TOKEN_TYPE(moo) != MOO_IOTOK_LPAREN) { moo_setsynerr (moo, MOO_SYNERR_LPAREN, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN(moo); if (is_token_word(moo, VOCA_ON)) { /* #pragma qc(on) */ moo->c->pragma_flags |= MOO_PRAGMA_QC; } else if (is_token_word(moo, VOCA_OFF)) { /* #pragma qc(off) */ moo->c->pragma_flags &= ~MOO_PRAGMA_QC; } else { moo_setsynerr (moo, MOO_SYNERR_DIRECTIVEINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN(moo); if (TOKEN_TYPE(moo) != MOO_IOTOK_RPAREN) { moo_setsynerr (moo, MOO_SYNERR_RPAREN, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } } else { moo_setsynerr (moo, MOO_SYNERR_PRAGMAINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } GET_TOKEN (moo); if (TOKEN_TYPE(moo) == MOO_IOTOK_PERIOD) goto done; } while (TOKEN_TYPE(moo) == MOO_IOTOK_COMMA); if (TOKEN_TYPE(moo) != MOO_IOTOK_PERIOD) { moo_setsynerr (moo, MOO_SYNERR_PERIOD, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } done: GET_TOKEN (moo); return 0; } static int compile_stream (moo_t* moo) { GET_TOKEN (moo); while (TOKEN_TYPE(moo) != MOO_IOTOK_EOF) { if (is_token_symbol(moo, VOCA_INCLUDE_S)) { /* #include 'xxxx' */ GET_TOKEN (moo); if (TOKEN_TYPE(moo) != MOO_IOTOK_STRLIT) { moo_setsynerr (moo, MOO_SYNERR_STRING, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } if (begin_include(moo) <= -1) return -1; } else if (is_token_symbol(moo, VOCA_PRAGMA_S)) { /*GET_TOKEN (moo);*/ if (compile_pragma_definition(moo) <= -1) return -1; } else if (is_token_word(moo, VOCA_CLASS)) { /* class Selfclass(Superclass) { } */ GET_TOKEN (moo); if (compile_class_definition(moo, CLASS_TYPE_NORMAL) <= -1) return -1; } else if (is_token_word(moo, VOCA_EXTEND)) { /* extend Selfclass {} */ GET_TOKEN (moo); if (compile_class_definition(moo, CLASS_TYPE_EXTEND) <= -1) return -1; } else if (is_token_word(moo, VOCA_INTERFACE)) { /* interface InterfaceName { } */ GET_TOKEN (moo); if (compile_interface_definition(moo) <= -1) return -1; } else if (is_token_word(moo, VOCA_POOLDIC)) { /* pooldic SharedPoolDic { ABC := 20. DEFG := 'ayz' } */ GET_TOKEN (moo); if (compile_pooldic_definition(moo) <= -1) return -1; } #if 0 else if (is_token_symbol(moo, VOCA_MAIN)) { /* #main */ /* TODO: implement this */ } #endif else { moo_setsynerr(moo, MOO_SYNERR_DIRECTIVEINVAL, TOKEN_LOC(moo), TOKEN_NAME(moo)); return -1; } } return 0; } static void gc_oopbuf (moo_t* moo, moo_oopbuf_t* oopbuf) { moo_oow_t i; for (i = 0; i < oopbuf->count; i++) { moo_oop_t x = moo_updateoopforgc(moo, oopbuf->ptr[i]); oopbuf->ptr[i] = x; } } static void gc_compiler (moo_t* moo) { /* called when garbage collection is performed */ if (moo->c) { gc_oopbuf (moo, &moo->c->arlit); gc_cunit_chain (moo); } } static void gc_cunit_chain (moo_t* moo) { moo_cunit_t* cunit; for (cunit = moo->c->cunit; cunit; cunit = cunit->cunit_parent) { switch (cunit->cunit_type) { case MOO_CUNIT_POOLDIC: { moo_cunit_pooldic_t* pd; pd = (moo_cunit_pooldic_t*)cunit; if (pd->pd_oop) { moo_oop_t x = moo_updateoopforgc(moo, (moo_oop_t)pd->pd_oop); pd->pd_oop = (moo_oop_dic_t)x; } if (pd->ns_oop) { moo_oop_t x = moo_updateoopforgc(moo, (moo_oop_t)pd->ns_oop); pd->ns_oop = (moo_oop_nsdic_t)x; } break; } case MOO_CUNIT_CLASS: { moo_oow_t i, j; moo_cunit_class_t* cc; cc = (moo_cunit_class_t*)cunit; if (cc->self_oop) cc->self_oop = (moo_oop_class_t)moo_updateoopforgc(moo, (moo_oop_t)cc->self_oop); if (cc->super_oop) cc->super_oop = moo_updateoopforgc(moo, cc->super_oop); for (i = 0; i < MOO_COUNTOF(cc->var); i++) { for (j = 0; j < cc->var[i].initv_count; j++) { register moo_oop_t x = cc->var[i].initv[j].v; if (x) cc->var[i].initv[j].v = moo_updateoopforgc(moo, x); } } if (cc->ns_oop) { moo_oop_t x = moo_updateoopforgc(moo, (moo_oop_t)cc->ns_oop); cc->ns_oop = (moo_oop_nsdic_t)x; } if (cc->superns_oop) { moo_oop_t x = moo_updateoopforgc(moo, (moo_oop_t)cc->superns_oop); cc->superns_oop = (moo_oop_nsdic_t)x; } gc_oopbuf (moo, &cc->ifces); gc_oopbuf (moo, &cc->ifce_mths[0]); gc_oopbuf (moo, &cc->ifce_mths[1]); gc_oopbuf (moo, &cc->pdimp.dics); gc_oopbuf (moo, &cc->mth.literals); break; } case MOO_CUNIT_INTERFACE: { moo_cunit_interface_t* ifce; ifce = (moo_cunit_interface_t*)cunit; if (ifce->self_oop) ifce->self_oop = (moo_oop_interface_t)moo_updateoopforgc(moo, (moo_oop_t)ifce->self_oop); if (ifce->ns_oop) { moo_oop_t x = moo_updateoopforgc(moo, (moo_oop_t)ifce->ns_oop); ifce->ns_oop = (moo_oop_nsdic_t)x; } gc_oopbuf (moo, &ifce->mth.literals); break; } default: /* nothing to do */ break; } } } static moo_cunit_t* push_cunit (moo_t* moo, moo_cunit_type_t type) { moo_cunit_t* cunit; moo_oow_t size; switch (type) { case MOO_CUNIT_POOLDIC: size = MOO_SIZEOF(moo_cunit_pooldic_t); break; case MOO_CUNIT_CLASS: size = MOO_SIZEOF(moo_cunit_class_t); break; case MOO_CUNIT_INTERFACE: size = MOO_SIZEOF(moo_cunit_interface_t); break; default: size = MOO_SIZEOF(moo_cunit_t); break; } cunit = (moo_cunit_t*)moo_callocmem(moo, size); if (!cunit) return MOO_NULL; cunit->cunit_type = type; cunit->cunit_parent = moo->c->cunit; moo->c->cunit = cunit; switch (type) { case MOO_CUNIT_POOLDIC: { /*moo_cunit_pooldic_t* pd; pd = (moo_cunit_pooldic_t*)cunit;*/ /* all fields are 0s or NULLs */ /* nothing to do */ break; } case MOO_CUNIT_CLASS: { moo_cunit_class_t* c; c = (moo_cunit_class_t*)cunit; c->indexed_type = MOO_OBJ_TYPE_OOP; /* whether indexed or not, it's the pointer type by default */ /* other fields are all 0s or NULLs */ break; } case MOO_CUNIT_INTERFACE: { /*moo_cunit_interface_t* ifce; ifce = (moo_cunit_interface_t*)cunit;*/ /* all fields are 0s or NULLs */ break; } default: break; } return cunit; } static void pop_cunit (moo_t* moo) { moo_cunit_t* cunit; MOO_ASSERT (moo, moo->c->cunit != MOO_NULL); cunit = moo->c->cunit; moo->c->cunit = cunit->cunit_parent; switch (cunit->cunit_type) { case MOO_CUNIT_POOLDIC: { moo_cunit_pooldic_t* pd; pd = (moo_cunit_pooldic_t*)cunit; if (pd->fqn.ptr) moo_freemem (moo, pd->fqn.ptr); break; } case MOO_CUNIT_CLASS: { moo_oow_t i; moo_cunit_class_t* cc; cc = (moo_cunit_class_t*)cunit; if (cc->fqn.ptr) moo_freemem (moo, cc->fqn.ptr); /* strbuf */ if (cc->superfqn.ptr) moo_freemem (moo, cc->superfqn.ptr); /* strbuf */ if (cc->modname.ptr) moo_freemem (moo, cc->modname.ptr); /* strbuf */ if (cc->ifces.ptr) moo_freemem (moo, cc->ifces.ptr); /* oopbuf */ if (cc->ifce_mths[0].ptr) moo_freemem (moo, cc->ifce_mths[0].ptr); /* oopbuf */ if (cc->ifce_mths[1].ptr) moo_freemem (moo, cc->ifce_mths[1].ptr); /* oopbuf */ for (i = 0; i < MOO_COUNTOF(cc->var); i++) { if (cc->var[i].str.ptr) moo_freemem (moo, cc->var[i].str.ptr); if (cc->var[i].initv) moo_freemem (moo, cc->var[i].initv); } clear_pooldic_import_data (moo, &cc->pdimp); clear_method_data (moo, &cc->mth); break; } case MOO_CUNIT_INTERFACE: { moo_cunit_interface_t* ifce; ifce = (moo_cunit_interface_t*)cunit; if (ifce->fqn.ptr) moo_freemem (moo, ifce->fqn.ptr); clear_method_data (moo, &ifce->mth); break; } default: break; } moo_freemem (moo, cunit); } static void fini_compiler (moo_t* moo) { /* called before the moo object is closed */ if (moo->c) { moo_clearcionames (moo); if (moo->c->tok.name.ptr) moo_freemem (moo, moo->c->tok.name.ptr); if (moo->c->balit.ptr) moo_freemem (moo, moo->c->balit.ptr); if (moo->c->arlit.ptr) moo_freemem (moo, moo->c->arlit.ptr); while (moo->c->cunit) pop_cunit (moo); if (moo->c->iid) moo_freemem (moo, moo->c->iid); moo_freemem (moo, moo->c); moo->c = MOO_NULL; } } static MOO_INLINE int _compile (moo_t* moo, moo_ioimpl_t io) { int n; if (!io) { moo_seterrbfmt (moo, MOO_EINVAL, "no IO implementation provided"); return -1; } if (!moo->c) { moo_evtcb_t cb, * cbp; MOO_MEMSET (&cb, 0, MOO_SIZEOF(cb)); cb.gc = gc_compiler; cb.fini = fini_compiler; cbp = moo_regevtcb (moo, &cb); if (!cbp) return -1; moo->c = moo_callocmem (moo, MOO_SIZEOF(*moo->c)); if (!moo->c) { moo_deregevtcb (moo, cbp); return -1; } moo->c->ilchr_ucs.ptr = &moo->c->ilchr; moo->c->ilchr_ucs.len = 1; } /* Some IO names could have been stored in earlier calls to this function. * I clear such names before i begin this function. i don't clear it * at the end of this function because it may be referenced as an error * location */ moo_clearcionames (moo); /* reset pragma flags */ moo->c->pragma_flags = 0; /* initialize some key fields */ moo->c->impl = io; moo->c->nungots = 0; /* The name field and the includer field are MOO_NULL * for the main stream */ MOO_MEMSET (&moo->c->arg, 0, MOO_SIZEOF(moo->c->arg)); moo->c->arg.line = 1; moo->c->arg.colm = 1; /* open the top-level stream */ n = moo->c->impl(moo, MOO_IO_OPEN, &moo->c->arg); if (n <= -1) return -1; /* the stream is open. set it as the current input stream */ moo->c->curinp = &moo->c->arg; /* compile the contents of the stream */ if (compile_stream(moo) <= -1) goto oops; /* close the stream */ MOO_ASSERT (moo, moo->c->curinp == &moo->c->arg); moo->c->impl (moo, MOO_IO_CLOSE, moo->c->curinp); return 0; oops: /* an error occurred and control has reached here * probably, some included files might not have been * closed. close them */ while (moo->c->curinp != &moo->c->arg) { moo_ioarg_t* prev; /* nothing much to do about a close error */ moo->c->impl (moo, MOO_IO_CLOSE, moo->c->curinp); prev = moo->c->curinp->includer; MOO_ASSERT (moo, moo->c->curinp->name != MOO_NULL); moo_freemem (moo, moo->c->curinp); moo->c->curinp = prev; } moo->c->impl (moo, MOO_IO_CLOSE, moo->c->curinp); return -1; } int moo_compile (moo_t* moo, moo_ioimpl_t io) { int n; int log_default_type_mask; log_default_type_mask = moo->log.default_type_mask; moo->log.default_type_mask |= MOO_LOG_COMPILER; n = _compile(moo, io); moo->log.default_type_mask = log_default_type_mask; return n; }