Skip to content

Commit be5a809

Browse files
committed
Add --yaml-stream
1 parent 064cb09 commit be5a809

File tree

5 files changed

+232
-50
lines changed

5 files changed

+232
-50
lines changed

cmd/jsonnet.cpp

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ void usage(std::ostream &o)
8787
o << " --code-file <var>=<val> As --file but file contents is Jsonnet code\n";
8888
o << " -o / --output-file <file> Write to the output file rather than stdout\n";
8989
o << " -m / --multi <dir> Write multiple files to the directory, list files on stdout\n";
90+
o << " -y / --yaml-stream Write output as a YAML stream of JSON documents\n";
9091
o << " -S / --string Expect a string, manifest as plain text\n";
9192
o << " -s / --max-stack <n> Number of allowed stack frames\n";
9293
o << " -t / --max-trace <n> Max length of stack trace before cropping\n";
@@ -148,6 +149,7 @@ struct JsonnetConfig {
148149

149150
// EVAL flags
150151
bool evalMulti;
152+
bool evalStream;
151153
std::string evalMultiOutputDir;
152154

153155
// FMT flags
@@ -157,6 +159,7 @@ struct JsonnetConfig {
157159
JsonnetConfig()
158160
: cmd(EVAL), filenameIsCode(false),
159161
evalMulti(false),
162+
evalStream(false),
160163
fmtInPlace(false),
161164
fmtTest(false)
162165
{ }
@@ -328,6 +331,8 @@ static bool process_args(int argc,
328331
output_dir += '/';
329332
}
330333
config->evalMultiOutputDir = output_dir;
334+
} else if (arg == "-y" || arg == "--yaml-stream") {
335+
config->evalStream = true;
331336
} else if (arg == "-S" || arg == "--string") {
332337
jsonnet_string_output(vm, 1);
333338
} else {
@@ -457,7 +462,8 @@ static bool read_input(JsonnetConfig* config, std::string* input) {
457462

458463
/** Writes output files for multiple file output */
459464
static bool write_multi_output_files(JsonnetVm* vm, char* output,
460-
const std::string& output_dir) {
465+
const std::string& output_dir)
466+
{
461467
// If multiple file output is used, then iterate over each string from
462468
// the sequence of strings returned by jsonnet_evaluate_snippet_multi,
463469
// construct pairs of filename and content, and write each output file.
@@ -513,6 +519,30 @@ static bool write_multi_output_files(JsonnetVm* vm, char* output,
513519
return true;
514520
}
515521

522+
/** Writes output files for multiple file output */
523+
static bool write_output_stream(JsonnetVm* vm, char* output)
524+
{
525+
// If multiple file output is used, then iterate over each string from
526+
// the sequence of strings returned by jsonnet_evaluate_snippet_multi,
527+
// construct pairs of filename and content, and write each output file.
528+
std::vector<std::string> r;
529+
for (const char *c=output ; *c!='\0' ; ) {
530+
const char *json = c;
531+
while (*c != '\0') ++c;
532+
++c;
533+
r.emplace_back(json);
534+
}
535+
jsonnet_realloc(vm, output, 0);
536+
for (const auto &str : r) {
537+
std::cout << "---\n";
538+
std::cout << str;
539+
}
540+
if (r.size() > 0)
541+
std::cout << "...\n";
542+
std::cout.flush();
543+
return true;
544+
}
545+
516546
/** Writes the output JSON to the specified output file for single-file
517547
* output
518548
*/
@@ -563,6 +593,9 @@ int main(int argc, const char **argv)
563593
if (config.evalMulti) {
564594
output = jsonnet_evaluate_snippet_multi(
565595
vm, config.inputFile.c_str(), input.c_str(), &error);
596+
} else if (config.evalStream) {
597+
output = jsonnet_evaluate_snippet_stream(
598+
vm, config.inputFile.c_str(), input.c_str(), &error);
566599
} else {
567600
output = jsonnet_evaluate_snippet(
568601
vm, config.inputFile.c_str(), input.c_str(), &error);
@@ -581,8 +614,12 @@ int main(int argc, const char **argv)
581614
if (!write_multi_output_files(vm, output, config.evalMultiOutputDir)) {
582615
return EXIT_FAILURE;
583616
}
617+
} else if (config.evalStream) {
618+
if (!write_output_stream(vm, output)) {
619+
return EXIT_FAILURE;
620+
}
584621
} else {
585-
bool successful = write_output_file(output, config.outputFile);
622+
bool successful = write_output_file(output, config.outputFile);
586623
jsonnet_realloc(vm, output, 0);
587624
if (!successful) {
588625
jsonnet_destroy(vm);

core/libjsonnet.cpp

Lines changed: 93 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -341,59 +341,90 @@ char *jsonnet_fmt_snippet(JsonnetVm *vm, const char *filename, const char *snipp
341341
}
342342

343343

344+
namespace {
345+
enum EvalKind { REGULAR, MULTI, STREAM };
346+
}
347+
344348
static char *jsonnet_evaluate_snippet_aux(JsonnetVm *vm, const char *filename,
345-
const char *snippet, int *error, bool multi)
349+
const char *snippet, int *error, EvalKind kind)
346350
{
347351
try {
348352
Allocator alloc;
349-
std::string json_str;
350353
AST *expr;
351-
std::map<std::string, std::string> files;
352354
Tokens tokens = jsonnet_lex(filename, snippet);
353355

354356
expr = jsonnet_parse(&alloc, tokens);
355357

356358
jsonnet_desugar(&alloc, expr);
357359

358360
jsonnet_static_analysis(expr);
359-
if (multi) {
360-
files = jsonnet_vm_execute_multi(&alloc, expr, vm->ext, vm->maxStack,
361-
vm->gcMinObjects, vm->gcGrowthTrigger,
362-
vm->importCallback, vm->importCallbackContext,
363-
vm->stringOutput);
364-
} else {
365-
json_str = jsonnet_vm_execute(&alloc, expr, vm->ext, vm->maxStack,
366-
vm->gcMinObjects, vm->gcGrowthTrigger,
367-
vm->importCallback, vm->importCallbackContext,
368-
vm->stringOutput);
369-
json_str += "\n";
370-
}
371-
372-
if (multi) {
373-
size_t sz = 1; // final sentinel
374-
for (const auto &pair : files) {
375-
sz += pair.first.length() + 1; // include sentinel
376-
sz += pair.second.length() + 2; // Add a '\n' as well as sentinel
361+
switch (kind) {
362+
case REGULAR: {
363+
std::string json_str = jsonnet_vm_execute(
364+
&alloc, expr, vm->ext, vm->maxStack, vm->gcMinObjects, vm->gcGrowthTrigger,
365+
vm->importCallback, vm->importCallbackContext, vm->stringOutput);
366+
json_str += "\n";
367+
*error = false;
368+
return from_string(vm, json_str);
377369
}
378-
char *buf = (char*)::malloc(sz);
379-
if (buf == nullptr) memory_panic();
380-
std::ptrdiff_t i = 0;
381-
for (const auto &pair : files) {
382-
memcpy(&buf[i], pair.first.c_str(), pair.first.length() + 1);
383-
i += pair.first.length() + 1;
384-
memcpy(&buf[i], pair.second.c_str(), pair.second.length());
385-
i += pair.second.length();
386-
buf[i] = '\n';
387-
i++;
388-
buf[i] = '\0';
389-
i++;
370+
break;
371+
372+
case MULTI: {
373+
std::map<std::string, std::string> files = jsonnet_vm_execute_multi(
374+
&alloc, expr, vm->ext, vm->maxStack, vm->gcMinObjects, vm->gcGrowthTrigger,
375+
vm->importCallback, vm->importCallbackContext, vm->stringOutput);
376+
size_t sz = 1; // final sentinel
377+
for (const auto &pair : files) {
378+
sz += pair.first.length() + 1; // include sentinel
379+
sz += pair.second.length() + 2; // Add a '\n' as well as sentinel
380+
}
381+
char *buf = (char*)::malloc(sz);
382+
if (buf == nullptr) memory_panic();
383+
std::ptrdiff_t i = 0;
384+
for (const auto &pair : files) {
385+
memcpy(&buf[i], pair.first.c_str(), pair.first.length() + 1);
386+
i += pair.first.length() + 1;
387+
memcpy(&buf[i], pair.second.c_str(), pair.second.length());
388+
i += pair.second.length();
389+
buf[i] = '\n';
390+
i++;
391+
buf[i] = '\0';
392+
i++;
393+
}
394+
buf[i] = '\0'; // final sentinel
395+
*error = false;
396+
return buf;
390397
}
391-
buf[i] = '\0'; // final sentinel
392-
*error = false;
393-
return buf;
394-
} else {
395-
*error = false;
396-
return from_string(vm, json_str);
398+
break;
399+
400+
case STREAM: {
401+
std::vector<std::string> documents = jsonnet_vm_execute_stream(
402+
&alloc, expr, vm->ext, vm->maxStack, vm->gcMinObjects, vm->gcGrowthTrigger,
403+
vm->importCallback, vm->importCallbackContext);
404+
size_t sz = 1; // final sentinel
405+
for (const auto &doc : documents) {
406+
sz += doc.length() + 2; // Add a '\n' as well as sentinel
407+
}
408+
char *buf = (char*)::malloc(sz);
409+
if (buf == nullptr) memory_panic();
410+
std::ptrdiff_t i = 0;
411+
for (const auto &doc : documents) {
412+
memcpy(&buf[i], doc.c_str(), doc.length());
413+
i += doc.length();
414+
buf[i] = '\n';
415+
i++;
416+
buf[i] = '\0';
417+
i++;
418+
}
419+
buf[i] = '\0'; // final sentinel
420+
*error = false;
421+
return buf;
422+
}
423+
break;
424+
425+
default:
426+
fputs("INTERNAL ERROR: bad value of 'kind', probably memory corruption.\n", stderr);
427+
abort();
397428
}
398429

399430
} catch (StaticError &e) {
@@ -423,7 +454,7 @@ static char *jsonnet_evaluate_snippet_aux(JsonnetVm *vm, const char *filename,
423454

424455
}
425456

426-
static char *jsonnet_evaluate_file_aux(JsonnetVm *vm, const char *filename, int *error, bool multi)
457+
static char *jsonnet_evaluate_file_aux(JsonnetVm *vm, const char *filename, int *error, EvalKind kind)
427458
{
428459
std::ifstream f;
429460
f.open(filename);
@@ -437,29 +468,37 @@ static char *jsonnet_evaluate_file_aux(JsonnetVm *vm, const char *filename, int
437468
input.assign(std::istreambuf_iterator<char>(f),
438469
std::istreambuf_iterator<char>());
439470

440-
return jsonnet_evaluate_snippet_aux(vm, filename, input.c_str(), error, multi);
471+
return jsonnet_evaluate_snippet_aux(vm, filename, input.c_str(), error, kind);
441472
}
442473

443474
char *jsonnet_evaluate_file(JsonnetVm *vm, const char *filename, int *error)
444475
{
445476
TRY
446-
return jsonnet_evaluate_file_aux(vm, filename, error, false);
477+
return jsonnet_evaluate_file_aux(vm, filename, error, REGULAR);
447478
CATCH("jsonnet_evaluate_file")
448479
return nullptr; // Never happens.
449480
}
450481

451482
char *jsonnet_evaluate_file_multi(JsonnetVm *vm, const char *filename, int *error)
452483
{
453484
TRY
454-
return jsonnet_evaluate_file_aux(vm, filename, error, true);
485+
return jsonnet_evaluate_file_aux(vm, filename, error, MULTI);
455486
CATCH("jsonnet_evaluate_file_multi")
456487
return nullptr; // Never happens.
457488
}
458489

490+
char *jsonnet_evaluate_file_stream(JsonnetVm *vm, const char *filename, int *error)
491+
{
492+
TRY
493+
return jsonnet_evaluate_file_aux(vm, filename, error, STREAM);
494+
CATCH("jsonnet_evaluate_file_stream")
495+
return nullptr; // Never happens.
496+
}
497+
459498
char *jsonnet_evaluate_snippet(JsonnetVm *vm, const char *filename, const char *snippet, int *error)
460499
{
461500
TRY
462-
return jsonnet_evaluate_snippet_aux(vm, filename, snippet, error, false);
501+
return jsonnet_evaluate_snippet_aux(vm, filename, snippet, error, REGULAR);
463502
CATCH("jsonnet_evaluate_snippet")
464503
return nullptr; // Never happens.
465504
}
@@ -468,11 +507,20 @@ char *jsonnet_evaluate_snippet_multi(JsonnetVm *vm, const char *filename,
468507
const char *snippet, int *error)
469508
{
470509
TRY
471-
return jsonnet_evaluate_snippet_aux(vm, filename, snippet, error, true);
510+
return jsonnet_evaluate_snippet_aux(vm, filename, snippet, error, MULTI);
472511
CATCH("jsonnet_evaluate_snippet_multi")
473512
return nullptr; // Never happens.
474513
}
475514

515+
char *jsonnet_evaluate_snippet_stream(JsonnetVm *vm, const char *filename,
516+
const char *snippet, int *error)
517+
{
518+
TRY
519+
return jsonnet_evaluate_snippet_aux(vm, filename, snippet, error, STREAM);
520+
CATCH("jsonnet_evaluate_snippet_stream")
521+
return nullptr; // Never happens.
522+
}
523+
476524
char *jsonnet_realloc(JsonnetVm *vm, char *str, size_t sz)
477525
{
478526
(void) vm;

core/vm.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2292,6 +2292,42 @@ namespace {
22922292
return r;
22932293
}
22942294

2295+
std::vector<std::string> manifestStream(void)
2296+
{
2297+
std::vector<std::string> r;
2298+
LocationRange loc("During manifestation");
2299+
if (scratch.t != Value::ARRAY) {
2300+
std::stringstream ss;
2301+
ss << "Stream mode: Top-level object was a " << type_str(scratch.t) << ", "
2302+
<< "should be an array whose elements hold "
2303+
<< "the JSON for each document in the stream.";
2304+
throw makeError(loc, ss.str());
2305+
}
2306+
auto *arr = static_cast<HeapArray*>(scratch.v.h);
2307+
for (auto *thunk : arr->elements) {
2308+
LocationRange tloc = thunk->body == nullptr
2309+
? loc
2310+
: thunk->body->location;
2311+
if (thunk->filled) {
2312+
stack.newCall(loc, thunk, nullptr, 0, BindingFrame{});
2313+
// Keep arr alive when scratch is overwritten
2314+
stack.top().val = scratch;
2315+
scratch = thunk->content;
2316+
} else {
2317+
stack.newCall(loc, thunk,
2318+
thunk->self, thunk->offset, thunk->upValues);
2319+
// Keep arr alive when scratch is overwritten
2320+
stack.top().val = scratch;
2321+
evaluate(thunk->body, stack.size());
2322+
}
2323+
String element = manifestJson(tloc, true, U"");
2324+
scratch = stack.top().val;
2325+
stack.pop();
2326+
r.push_back(encode_utf8(element));
2327+
}
2328+
return r;
2329+
}
2330+
22952331
};
22962332

22972333
}
@@ -2324,3 +2360,14 @@ StrMap jsonnet_vm_execute_multi(Allocator *alloc, const AST *ast, const ExtMap &
23242360
return vm.manifestMulti(string_output);
23252361
}
23262362

2363+
std::vector<std::string> jsonnet_vm_execute_stream(
2364+
Allocator *alloc, const AST *ast, const ExtMap &ext_vars, unsigned max_stack,
2365+
double gc_min_objects, double gc_growth_trigger, JsonnetImportCallback *import_callback,
2366+
void *ctx)
2367+
{
2368+
Interpreter vm(alloc, ext_vars, max_stack, gc_min_objects, gc_growth_trigger,
2369+
import_callback, ctx);
2370+
vm.evaluate(ast, 0);
2371+
return vm.manifestStream();
2372+
}
2373+

core/vm.h

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ std::string jsonnet_vm_execute(Allocator *alloc, const AST *ast,
7373
JsonnetImportCallback *import_callback, void *import_callback_ctx,
7474
bool string_output);
7575

76-
/** Execute the program and return the value as a number of JSON files.
76+
/** Execute the program and return the value as a number of named JSON files.
7777
*
7878
* This assumes the given program yields an object whose keys are filenames.
7979
*
@@ -95,4 +95,26 @@ std::map<std::string, std::string> jsonnet_vm_execute_multi(
9595
JsonnetImportCallback *import_callback, void *import_callback_ctx,
9696
bool string_output);
9797

98+
/** Execute the program and return the value as a stream of JSON files.
99+
*
100+
* This assumes the given program yields an array whose elements are individual
101+
* JSON files.
102+
*
103+
* \param alloc The allocator used to create the ast.
104+
* \param ast The program to execute.
105+
* \param ext The external vars / code.
106+
* \param max_stack Recursion beyond this level gives an error.
107+
* \param gc_min_objects The garbage collector does not run when the heap is this small.
108+
* \param gc_growth_trigger Growth since last garbage collection cycle to trigger a new cycle.
109+
* \param import_callback A callback to handle imports
110+
* \param import_callback_ctx Context param for the import callback.
111+
* \param output_string Whether to expect a string and output it without JSON encoding
112+
* \throws RuntimeError reports runtime errors in the program.
113+
* \returns A mapping from filename to the JSON strings for that file.
114+
*/
115+
std::vector<std::string> jsonnet_vm_execute_stream(
116+
Allocator *alloc, const AST *ast, const std::map<std::string, VmExt> &ext,
117+
unsigned max_stack, double gc_min_objects, double gc_growth_trigger,
118+
JsonnetImportCallback *import_callback, void *import_callback_ctx);
119+
98120
#endif

0 commit comments

Comments
 (0)