HempBool hemp_document_scan( HempDocument document ) { hemp_debug_call("hemp_document_scan(%p)\n", document); if (! document->dialect->scanner) hemp_fatal("No scanner defined for %s document\n", document->dialect->name); if (! document->source->text) hemp_source_read(document->source); document->scanptr = document->scantok = document->source->text; document->scanpos = 0; if (! document->scantags) document->scantags = hemp_stack_new(HEMP_SCANTAGS_SIZE); return document->dialect->scanner(document) ? HEMP_TRUE : HEMP_FALSE; // return hemp_action_run(document->scanner, document) // ? HEMP_TRUE // : HEMP_FALSE; }
HempValue hemp_document_data( HempDocument document, HempContext context ) { hemp_debug_call("hemp_document_data(%p)\n", document); HempBool my_context = HEMP_FALSE; HempFragment root = hemp_document_tree(document); HempValue values; HempList results; if (! root) hemp_fatal("document does not have a root element"); if (! context) { my_context = HEMP_TRUE; context = hemp_context_new(document->hemp); } values = root->type->values(hemp_frag_val(root), context, HempNothing); results = hemp_val_list(values); if (my_context) hemp_context_free(context); if (results->length > 1) { return values; } else if (results->length == 1) { return hemp_list_item(results, 0); } else { return HempEmpty; } }
HempText hemp_document_process( HempDocument document, HempContext context, HempText output ) { hemp_debug_call("hemp_document_process(%p)\n", document); HempBool my_context = HEMP_FALSE; HempFragment root = hemp_document_tree(document); if (! context) { my_context = HEMP_TRUE; context = hemp_context_new(document->hemp); } if (! root) hemp_fatal("document does not have a root element"); if (! output) output = hemp_text_new(); root->type->text(hemp_frag_val(root), context, hemp_text_val(output)); // HEMP_CATCH_ALL; // hemp_fatal("Error processing document: %s", hemp->error->message); // HEMP_END; if (my_context) hemp_context_free(context); return output; }
HEMP_INLINE void hemp_scan_number( HempDocument document ) { HempString src = document->scanptr; HempNum num_val = 0; HempInt int_val = 0; HempBool is_int = HEMP_FALSE; HempFragment fragment; // hemp_debug_scan("scanning number: %s\n", document->scanptr); /* number - try integer first */ errno = 0; int_val = strtol(document->scanptr, &src, 0); is_int = HEMP_TRUE; /* If there's a decimal point and a digit following then it's a ** floating point number. We also look out for e/E which also ** indicate fp numbers in scientific notation, e.g. 1.23e6. ** Note that we don't accept the decimal point if the next ** character is not a digit. This is required to support methods ** called against numeric constants, e.g. 12345.hex */ if ( ( *src == '.' && isdigit(*(src + 1)) ) || ( *src == 'e' || *src == 'E' ) ) { is_int = HEMP_FALSE; num_val = strtod(document->scanptr, &src); } if (errno == ERANGE) { /* TODO: trim next token out of text */ hemp_throw(document->hemp, HEMP_ERROR_OVERFLOW, document->scanptr); } else if (errno) { /* should never happen (famous last words) as we pre-check ** that there is at least one valid digit available to be ** parsed, but we check anyway */ hemp_fatal("Unknown number parsing error: %d", errno); } else if (is_int) { fragment = hemp_document_scanned_to( document, HempElementInteger, src ); fragment->op.value = hemp_int_val(int_val); } else { fragment = hemp_document_scanned_to( document, HempElementNumber, src ); fragment->op.value = hemp_num_val(num_val); } }
HEMP_INLINE HempFragments hemp_fragment_fragments( HempFragment fragment ) { if (! fragment->fragments) hemp_fatal( "No fragments defined for %s fragment", fragment->type->name ); return fragment->fragments; }
HEMP_INLINE HempGrammar hemp_fragment_grammar( HempFragment fragment ) { if (! fragment->type->grammar) hemp_fatal( "No grammar defined for %s fragment", fragment->type->name ); return fragment->type->grammar; }
HempString hemp_scheme_file_reader( HempSource source ) { source->text = hemp_filesystem_read_file(source->name); if (! source->text) { hemp_fatal("failed to read file: %s\n", source->name); // fixme } return source->text; }
HempTestPlan hemp_test_global_plan( HempUint planned ) { HEMP_MUTEX_LOCK; if (hemp_global_test_plan) hemp_fatal("A global test plan is already defined"); hemp_global_test_plan = hemp_test_plan_setup(planned); HEMP_MUTEX_UNLOCK; return hemp_global_test_plan; }
int hemp_test_done( HempTestPlan plan ) { if (! plan) plan = hemp_global_test_plan; if (! plan) hemp_fatal("No test plan defined"); int r = hemp_test_plan_result(plan); hemp_test_plan_summary(plan); hemp_test_plan_cleanup(plan); return r; }
void hemp_mem_trace_ok(void) { char *debug = getenv("HEMP_MEMORY_TRACE"); HempSize size = hemp_mem_trace_report( debug && hemp_string_eq(debug, "1") ); if (size == 0) { printf("%sall memory freed%s\n", HEMP_COL_PASS, HEMP_COL_TERM); } else if (size == -1) { printf("%smemory checks disabled%s\n", HEMP_COL_SKIP, HEMP_COL_TERM); } else { printf("%smemory unfreed: %lu bytes%s\n", HEMP_COL_FAIL, size, HEMP_COL_TERM); hemp_mem_trace_report(HEMP_TRUE); hemp_fatal("Fix the memory leaks!"); } }
HempUint hemp_test_expect_text( HempString language, HempString dialect, HempString text, HempString alias, HempContext context ) { Hemp hemp = hemp_new(); HempString test, name, expect, error, end; HempList list; HempDocument document; HempText output; HempSize n; HEMP_TRY; hemp_language(hemp, language); HEMP_CATCH_ALL; hemp_fatal("Failed to load hemp language '%s': %s", language, hemp->error->message); HEMP_END; if ((test = strstr(text, HEMP_TEST_START))) { hemp_string_to_next_line(&test); } else { test = text; } if ((end = strstr(text, HEMP_TEST_STOP))) *end = HEMP_NUL; test = strstr(test, HEMP_TEST_MARKER); if (! test) hemp_fatal("no tests found in %s", alias); /* skip over first -- test */ test += strlen(HEMP_TEST_MARKER); list = hemp_string_split(test, HEMP_TEST_MARKER); hemp_debug("found %d tests in %s\n", list->length, alias); // hemp_test_plan(list->length * 2); hemp_test_plan(list->length); for (n = 0; n < list->length; n++) { test = hemp_val_str( hemp_list_item(list, n) ); /* skip over leading whitespace */ while (isspace(*test)) { test++; } name = test; do { test++; } while (*test != HEMP_LF && *test != HEMP_CR); /* NUL terminate test name */ *test = '\0'; do { test++; } while (*test == HEMP_LF || *test == HEMP_CR); if ((expect = strstr(test, HEMP_EXPECT_MARKER))) { *expect = '\0'; expect += strlen(HEMP_EXPECT_MARKER); hemp_string_to_next_line(&expect); } if ( (error = strstr(test, HEMP_ERROR_MARKER)) || (expect && (error = strstr(expect, HEMP_ERROR_MARKER )))) { *error = '\0'; error += strlen(HEMP_ERROR_MARKER); hemp_string_to_next_line(&error); } hemp_string_chomp(test); // printf(">> test %u: %s\n", n, name); // if (expect) // printf(">> expect [%s]\n", expect); // if (error) // printf(">> error [%s]\n", error); HEMP_TRY; document = hemp_document( hemp, HEMP_TT3, HEMP_TEXT, test ); output = hemp_document_render(document, context); // ok( output, "%s (rendered)", name); if (expect) hemp_test_output(name, output, expect); else if (error) fail("expected error but got output"); HEMP_CATCH_ALL; output = hemp_error_text(hemp->error); if (error) { // pass("%s", name); hemp_test_error(name, output->string, error); } else { fail("%s (error: %s)", name, output->string); // fail("error: %s", output->string); } HEMP_END; hemp_text_free(output); } hemp_list_free(list); hemp_free(hemp); return hemp_test_done(NULL); }
HempUint hemp_test_result( HempTestPlan plan, HempBool ok, /* true=pass false=fail */ HempName func, HempName file, /* source file */ HempUint line, /* line number */ HempName name, /* test name */ ... ) { va_list ap; HempString fullname = NULL; HempBool badname = HEMP_TRUE; /* assume the worst */ char *c; HEMP_MUTEX_LOCK; if (! plan) plan = hemp_global_test_plan; if (! plan) hemp_fatal("No test plan"); plan->tested++; /* Start by taking the test name and performing any printf() expansions on it */ if (name) { va_start(ap, name); vasprintf(&fullname, name, ap); va_end(ap); /* Make sure the test name contains more than digits and spaces. Emit an error message and exit if it does */ if (! fullname) hemp_mem_fail("test name"); for(c = fullname; *c != HEMP_NUL; c++) { if(!isdigit(*c) && !isspace(*c)) { badname = HEMP_FALSE; break; } } if (badname) { hemp_fatal( " You named your test '%s'. You shouldn't use numbers for test names.", fullname ); } } if (ok) { printf( "%sok %lu", HEMP_COL_PASS, plan->tested ); plan->passed++; } else { printf( "%snot ok %lu", plan->todo ? HEMP_COL_SKIP : HEMP_COL_FAIL, plan->tested ); plan->failed++; } if (name) { /* Print the test name, escaping any '#' characters */ printf(" - "); flockfile(stdout); for(c = fullname; *c != HEMP_NUL; c++) { if (*c == '#') fputc('\\', stdout); fputc((int) *c, stdout); } funlockfile(stdout); } if (plan->todo) { printf(" # TODO %s", plan->todo_msg); /* TODO items aren't considered failures, so decrement the counter */ if (! ok) plan->failed--; } printf("%s\n", HEMP_COL_TERM); if (! ok && plan->verbose) hemp_debug_cyan( " Failed %stest (%s:%s() at line %d)", plan->todo ? "(TODO) " : "", file, func, line ); if (fullname) free(fullname); if (plan->todo) { if (--plan->todo == 0) free(plan->todo_msg); } HEMP_MUTEX_UNLOCK; /* We only care (when testing) that ok is positive, but here we specifically only want to return 1 or 0 */ return ok ? 1 : 0; }
HempModule hemp_use_module( Hemp hemp, HempString type, HempString name ) { hemp_debug_call("hemp_use_module(%s => %s)\n", type, name); HempValue path = hemp_config_get(hemp, HEMP_CONFIG_MODPATH); HempString string; if (hemp_is_defined(path)) { string = hemp_to_string(path, hemp->context); // hemp_debug_msg("already got %s: %s\n", HEMP_CONFIG_MODPATH, string); } else { HempValue dir = hemp_config_get(hemp, HEMP_CONFIG_DIR); HempValue mod = hemp_config_get(hemp, HEMP_CONFIG_MODDIR); HempString dstr = hemp_to_string(dir, hemp->context); HempString mstr = hemp_to_string(mod, hemp->context); string = hemp_uri_path_join(dstr, mstr, 1); // hemp_debug_msg("constructed %s: %s\n", HEMP_CONFIG_MODPATH, string); /* ugly work-around so we can get the context to manage memory */ HempText text = hemp_context_tmp_text_size(hemp->context, strlen(string) + 1); hemp_text_append_string(text, string); hemp_mem_free(string); string = text->string; // hemp_config_set(hemp, HEMP_CONFIG_MODPATH, hemp_text_val(text)); } // TODO: need a way to save dotted path (hemp.module_path) back into config /* quick hack to get something working */ HempString modpath = getenv("HEMP_MODULE_PATH"); if (! modpath || ! *modpath) { modpath = string; // hemp_debug_msg("No HEMP_MODULE_PATH environment variable set\n"); // return HEMP_FALSE; } /* TODO: sort this mess out */ HempString tpath = hemp_uri_path_join(modpath, type, 1); HempString mpath = hemp_uri_path_join(tpath, name, 1); HempText mtext = hemp_text_from_string(mpath); hemp_text_append_string(mtext, HEMP_MODULE_EXT); hemp_mem_free(mpath); hemp_mem_free(tpath); // hemp_debug_msg("constructed module path: %s\n", mtext->string); HempModule module = hemp_global_module(hemp->global, mtext->string); if (module->binder) { module->binder(module, hemp); } else if (module->error) { hemp_fatal(module->error); } else { /* should never happen - famous last word */ hemp_fatal("No binder function for %s module", name); } hemp_text_free(mtext); return module; }