#include "moar.h" /* This representation's function pointer table. */ static const MVMREPROps Decoder_this_repr; /* Creates a new type object of this representation, and associates it with * the given HOW. */ static MVMObject * type_object_for(MVMThreadContext *tc, MVMObject *HOW) { MVMSTable *st = MVM_gc_allocate_stable(tc, &Decoder_this_repr, HOW); MVMROOT(tc, st, { MVMObject *obj = MVM_gc_allocate_type_object(tc, st); MVM_ASSIGN_REF(tc, &(st->header), st->WHAT, obj); st->size = sizeof(MVMDecoder); }); return st->WHAT; } /* Copies the body of one object to another. */ static void copy_to(MVMThreadContext *tc, MVMSTable *st, void *src, MVMObject *dest_root, void *dest) { MVM_exception_throw_adhoc(tc, "Cannot copy object with representation Decoder"); } /* Called by the VM to mark any GCable items. */ static void gc_mark(MVMThreadContext *tc, MVMSTable *st, void *data, MVMGCWorklist *worklist) { MVMDecoderBody *decoder = (MVMDecoderBody*)data; if (decoder->ds) { MVM_gc_worklist_add(tc, worklist, &(decoder->ds->replacement)); } } /* Called by the VM in order to free memory associated with this object. */ static void gc_free(MVMThreadContext *tc, MVMObject *obj) { MVMDecoder *decoder = (MVMDecoder *)obj; if (decoder->body.ds) MVM_string_decodestream_destroy(tc, decoder->body.ds); if (decoder->body.sep_spec) MVM_string_decode_stream_sep_destroy(tc, decoder->body.sep_spec); MVM_free(decoder->body.sep_spec); } static const MVMStorageSpec storage_spec = { MVM_STORAGE_SPEC_REFERENCE, /* inlineable */ 0, /* bits */ 0, /* align */ MVM_STORAGE_SPEC_BP_NONE, /* boxed_primitive */ 0, /* can_box */ 0, /* is_unsigned */ }; /* Gets the storage specification for this representation. */ static const MVMStorageSpec * get_storage_spec(MVMThreadContext *tc, MVMSTable *st) { return &storage_spec; } /* Compose the representation. */ static void compose(MVMThreadContext *tc, MVMSTable *st, MVMObject *info) { /* Nothing to do for this REPR. */ } /* Set the size of the STable. */ static void deserialize_stable_size(MVMThreadContext *tc, MVMSTable *st, MVMSerializationReader *reader) { st->size = sizeof(MVMDecoder); } /* Initializes the representation. */ const MVMREPROps * MVMDecoder_initialize(MVMThreadContext *tc) { return &Decoder_this_repr; } static const MVMREPROps Decoder_this_repr = { type_object_for, MVM_gc_allocate_object, NULL, /* initialize */ copy_to, MVM_REPR_DEFAULT_ATTR_FUNCS, MVM_REPR_DEFAULT_BOX_FUNCS, MVM_REPR_DEFAULT_POS_FUNCS, MVM_REPR_DEFAULT_ASS_FUNCS, MVM_REPR_DEFAULT_ELEMS, get_storage_spec, NULL, /* change_type */ NULL, /* serialize */ NULL, /* deserialize */ NULL, /* serialize_repr_data */ NULL, /* deserialize_repr_data */ deserialize_stable_size, gc_mark, gc_free, NULL, /* gc_cleanup */ NULL, /* gc_mark_repr_data */ NULL, /* gc_free_repr_data */ compose, NULL, /* spesh */ "Decoder", /* name */ MVM_REPR_ID_Decoder, NULL, /* unmanaged_size */ NULL, /* describe_refs */ }; /* Assert that the passed object really is a decoder; throw if not. */ void MVM_decoder_ensure_decoder(MVMThreadContext *tc, MVMObject *decoder, const char *op) { if (MVM_UNLIKELY(REPR(decoder)->ID != MVM_REPR_ID_Decoder || !IS_CONCRETE(decoder))) MVM_exception_throw_adhoc(tc, "Operation '%s' can only work on an object with the Decoder representation", op); } /* Checks and sets the decoder single-user sanity check flag. */ static void enter_single_user(MVMThreadContext *tc, MVMDecoder *decoder) { if (!MVM_trycas(&(decoder->body.in_use), 0, 1)) MVM_exception_throw_adhoc(tc, "Decoder may not be used concurrently"); MVM_tc_set_ex_release_atomic(tc, &(decoder->body.in_use)); } /* Releases the decoder single-user sanity check flag. */ static void exit_single_user(MVMThreadContext *tc, MVMDecoder *decoder) { decoder->body.in_use = 0; MVM_tc_clear_ex_release_mutex(tc); } /* Configures the decoder with the specified encoding and other configuration. */ static int should_translate_newlines(MVMThreadContext *tc, MVMObject *config) { if (IS_CONCRETE(config) && REPR(config)->ID == MVM_REPR_ID_MVMHash) { MVMObject *value = MVM_repr_at_key_o(tc, config, tc->instance->str_consts.translate_newlines); return IS_CONCRETE(value) && MVM_repr_get_int(tc, value) != 0; } return 0; } static MVMString * has_replacement(MVMThreadContext *tc, MVMObject *config) { if (IS_CONCRETE(config) && REPR(config)->ID == MVM_REPR_ID_MVMHash) { MVMObject *value = MVM_repr_at_key_o(tc, config, tc->instance->str_consts.replacement); return IS_CONCRETE(value) ? MVM_repr_get_str(tc, value) : NULL; } return NULL; } static int has_config(MVMThreadContext *tc, MVMObject *config) { if (IS_CONCRETE(config) && REPR(config)->ID == MVM_REPR_ID_MVMHash) { MVMObject *value = MVM_repr_at_key_o(tc, config, tc->instance->str_consts.config); return IS_CONCRETE(value) ? MVM_repr_get_int(tc, value) : 0; } return 0; } void MVM_decoder_configure(MVMThreadContext *tc, MVMDecoder *decoder, MVMString *encoding, MVMObject *config) { if (!decoder->body.ds) { MVMuint8 encid = MVM_string_find_encoding(tc, encoding); enter_single_user(tc, decoder); decoder->body.ds = MVM_string_decodestream_create(tc, encid, 0, should_translate_newlines(tc, config)); decoder->body.sep_spec = MVM_malloc(sizeof(MVMDecodeStreamSeparators)); MVM_string_decode_stream_sep_default(tc, decoder->body.sep_spec); MVM_ASSIGN_REF(tc, &(decoder->common.header), decoder->body.ds->replacement, has_replacement(tc, config)); decoder->body.ds->config = has_config(tc, config); exit_single_user(tc, decoder); } else { MVM_exception_throw_adhoc(tc, "Decoder already configured"); } } /* Obtains the DecodeStream object provided it's initialized, throwing if not. */ static MVMDecodeStream * get_ds(MVMThreadContext *tc, MVMDecoder *decoder) { MVMDecodeStream *ds = decoder->body.ds; if (!ds) MVM_exception_throw_adhoc(tc, "Decoder not yet configured"); return ds; } /* Gets the separators specification for the decoder. */ MVM_STATIC_INLINE MVMDecodeStreamSeparators * get_sep_spec(MVMThreadContext *tc, MVMDecoder *decoder) { return decoder->body.sep_spec; } /* Sets the separators to be used by this decode stream. */ void MVM_decoder_set_separators(MVMThreadContext *tc, MVMDecoder *decoder, MVMObject *seps) { MVMint32 is_str_array = REPR(seps)->pos_funcs.get_elem_storage_spec(tc, STABLE(seps)).boxed_primitive == MVM_STORAGE_SPEC_BP_STR; get_ds(tc, decoder); /* Ensure we're sufficiently initialized. */ if (is_str_array) { MVMString **c_seps = NULL; MVMuint64 i; MVMuint64 num_seps = MVM_repr_elems(tc, seps); if (num_seps > 0xFFFFFF) MVM_exception_throw_adhoc(tc, "Too many line separators"); c_seps = MVM_malloc((num_seps ? num_seps : 1) * sizeof(MVMString *)); for (i = 0; i < num_seps; i++) c_seps[i] = MVM_repr_at_pos_s(tc, seps, i); enter_single_user(tc, decoder); MVM_string_decode_stream_sep_from_strings(tc, get_sep_spec(tc, decoder), c_seps, num_seps); exit_single_user(tc, decoder); MVM_free(c_seps); } else { MVM_exception_throw_adhoc(tc, "Set separators requires a native string array"); } } /* Adds bytes to the decode stream. */ void MVM_decoder_add_bytes(MVMThreadContext *tc, MVMDecoder *decoder, MVMObject *buffer) { MVMDecodeStream *ds = get_ds(tc, decoder); if (REPR(buffer)->ID == MVM_REPR_ID_VMArray) { /* To be safe, we need to make a copy of data in a resizable array; it * may change/move under us. */ MVMuint8 *output = NULL, *copy = NULL; MVMint64 output_size; switch (((MVMArrayREPRData *)STABLE(buffer)->REPR_data)->slot_type) { case MVM_ARRAY_U8: case MVM_ARRAY_I8: output = (MVMuint8 *)(((MVMArray *)buffer)->body.slots.u8 + ((MVMArray *)buffer)->body.start); output_size = ((MVMArray *)buffer)->body.elems; break; case MVM_ARRAY_U16: case MVM_ARRAY_I16: output = (MVMuint8 *)(((MVMArray *)buffer)->body.slots.u16 + ((MVMArray *)buffer)->body.start); output_size = ((MVMArray *)buffer)->body.elems * 2; break; case MVM_ARRAY_U32: case MVM_ARRAY_I32: output = (MVMuint8 *)(((MVMArray *)buffer)->body.slots.u32 + ((MVMArray *)buffer)->body.start); output_size = ((MVMArray *)buffer)->body.elems * 4; break; default: MVM_exception_throw_adhoc(tc, "Can only add bytes from an int array to a decoder"); } copy = MVM_malloc(output_size); memcpy(copy, output, output_size); enter_single_user(tc, decoder); MVM_string_decodestream_add_bytes(tc, ds, copy, output_size); exit_single_user(tc, decoder); } else { MVM_exception_throw_adhoc(tc, "Cannot add bytes to a decoder with a %s", REPR(buffer)->name); } } /* Takes the specified number of chars from the decoder, or all if there * is not enough. */ MVMString * MVM_decoder_take_chars(MVMThreadContext *tc, MVMDecoder *decoder, MVMint64 chars, MVMint64 eof) { MVMString *result = NULL; enter_single_user(tc, decoder); MVMROOT(tc, decoder, { result = MVM_string_decodestream_get_chars(tc, get_ds(tc, decoder), (MVMint32)chars, eof); }); exit_single_user(tc, decoder); return result; } /* Takes all chars from the decoder. */ MVMString * MVM_decoder_take_all_chars(MVMThreadContext *tc, MVMDecoder *decoder) { MVMString *result = NULL; enter_single_user(tc, decoder); MVMROOT(tc, decoder, { result = MVM_string_decodestream_get_all(tc, get_ds(tc, decoder)); }); exit_single_user(tc, decoder); return result; } /* Takes all available chars from the decoder. */ MVMString * MVM_decoder_take_available_chars(MVMThreadContext *tc, MVMDecoder *decoder) { MVMString *result = NULL; enter_single_user(tc, decoder); MVMROOT(tc, decoder, { result = MVM_string_decodestream_get_available(tc, get_ds(tc, decoder)); }); exit_single_user(tc, decoder); return result; } /* Takes a line from the decoder. */ MVMString * MVM_decoder_take_line(MVMThreadContext *tc, MVMDecoder *decoder, MVMint64 chomp, MVMint64 incomplete_ok) { MVMDecodeStream *ds = get_ds(tc, decoder); MVMDecodeStreamSeparators *sep_spec = get_sep_spec(tc, decoder); MVMString *result = NULL; enter_single_user(tc, decoder); MVMROOT(tc, decoder, { result = incomplete_ok ? MVM_string_decodestream_get_until_sep_eof(tc, ds, sep_spec, (MVMint32)chomp) : MVM_string_decodestream_get_until_sep(tc, ds, sep_spec, (MVMint32)chomp); }); exit_single_user(tc, decoder); return result; } /* Returns true if the decoder is empty. */ MVMint64 MVM_decoder_empty(MVMThreadContext *tc, MVMDecoder *decoder) { return MVM_string_decodestream_is_empty(tc, get_ds(tc, decoder)); } /* Gets the number of (undecoded) bytes available in the decoder. */ MVMint64 MVM_decoder_bytes_available(MVMThreadContext *tc, MVMDecoder *decoder) { return MVM_string_decodestream_bytes_available(tc, get_ds(tc, decoder)); } /* Takes bytes from the decode stream and places them into a buffer. If there * are less available than requested, hand back null. */ MVMObject * MVM_decoder_take_bytes(MVMThreadContext *tc, MVMDecoder *decoder, MVMObject *buf_type, MVMint64 bytes) { MVMDecodeStream *ds = get_ds(tc, decoder); MVMuint8 *buf = NULL; MVMint64 read; MVMObject *result = NULL; /* Ensure the target is in the correct form. */ if (REPR(buf_type)->ID != MVM_REPR_ID_VMArray) MVM_exception_throw_adhoc(tc, "decodertakebytes requires a native array type"); if (((MVMArrayREPRData *)STABLE(buf_type)->REPR_data)->slot_type != MVM_ARRAY_U8 && ((MVMArrayREPRData *)STABLE(buf_type)->REPR_data)->slot_type != MVM_ARRAY_I8) MVM_exception_throw_adhoc(tc, "decodertakebytes requires a native array type of uint8 or int8"); if (bytes < 0 || bytes > 0x7FFFFFFF) MVM_exception_throw_adhoc(tc, "Out of range: attempted to read %"PRId64" bytes from decoder", bytes); if (MVM_string_decodestream_bytes_available(tc, ds) < bytes) return tc->instance->VMNull; enter_single_user(tc, decoder); read = MVM_string_decodestream_bytes_to_buf(tc, ds, &buf, bytes); exit_single_user(tc, decoder); /* Allocate after we're done with decoder to avoid having to MVMROOT */ result = MVM_repr_alloc_init(tc, buf_type); ((MVMArray *)result)->body.slots.i8 = (MVMint8 *)buf; ((MVMArray *)result)->body.start = 0; ((MVMArray *)result)->body.ssize = read; ((MVMArray *)result)->body.elems = read; return result; }