Skip to content

Commit a58dba6

Browse files
authored
Merge pull request #1569 from stan-dev/stancjs-request-colors
Allow requesting ANSI colors in stanc.js
2 parents 25ac971 + 22f0d0f commit a58dba6

File tree

4 files changed

+119
-92
lines changed

4 files changed

+119
-92
lines changed

src/stancjs/stancjs.ml

Lines changed: 103 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,43 @@ let invoke_driver model_name model flags =
2323

2424
let wrap_warnings ~warnings =
2525
( "warnings"
26-
, Js.Unsafe.inject (Js.array (List.to_array (List.map ~f:Js.string warnings)))
26+
, Js.Unsafe.coerce (Js.array (List.to_array (List.map ~f:Js.string warnings)))
2727
)
2828

2929
let wrap_error ~warnings e =
3030
Js.Unsafe.obj
31-
[| ("errors", Js.Unsafe.inject (Array.map ~f:Js.string [| e |]))
31+
[| ( "errors"
32+
, (* NB: The "0" entry is due to a historical mistake that led
33+
the first entry always being a 0 (this element is a 'tag' used by jsoo
34+
internally, but was not meant to be exposed to the user).
35+
For backward compatibility with existing consumers
36+
of stanc.js we have to keep this behavior.
37+
*)
38+
Js.Unsafe.coerce (Js.array (Array.map ~f:Js.string [| "0"; e |])) )
3239
; wrap_warnings ~warnings |]
3340

34-
let wrap_result ?printed_filename ~code ~warnings res =
41+
(** similar to [Fmt.str_like] but directly sets style
42+
rendering rather than copying from another ppf *)
43+
let str_color ~color_output =
44+
let buf = Buffer.create 64 in
45+
let ppf = Format.formatter_of_buffer buf in
46+
Fmt.set_style_renderer ppf (if color_output then `Ansi_tty else `None);
47+
let flush ppf =
48+
Format.pp_print_flush ppf ();
49+
let s = Buffer.contents buf in
50+
Buffer.reset buf;
51+
s in
52+
Format.kfprintf flush ppf
53+
54+
let wrap_result ?printed_filename ~color_output ~code ~warnings res =
3555
match res with
3656
| Result.Ok s ->
3757
Js.Unsafe.obj
38-
[| ("result", Js.Unsafe.inject (Js.string s)); wrap_warnings ~warnings
58+
[| ("result", Js.Unsafe.coerce (Js.string s)); wrap_warnings ~warnings
3959
|]
4060
| Error e ->
4161
let e =
42-
Fmt.str "%a"
62+
str_color ~color_output "%a"
4363
(Errors.pp ?printed_filename ?code:(Some (Js.to_string code)))
4464
e in
4565
wrap_error ~warnings e
@@ -66,7 +86,7 @@ let checked_to_array ~name value =
6686
(Js.Unsafe.meth_call
6787
(Js.Unsafe.pure_js_expr "Array")
6888
"isArray"
69-
[| Js.Unsafe.inject value |]))
89+
[| Js.Unsafe.coerce value |]))
7090
then
7191
Error
7292
(Fmt.str
@@ -105,9 +125,10 @@ let get_includes includes : string String.Map.t * string list =
105125
(Fmt.str "Warning: stanc.js failed to parse included file mapping:@ %s")
106126
warnings )
107127

108-
(** Turn our array of flags into a Driver.Flats.t *)
109-
let process_flags (flags : 'a Js.opt) includes : (Driver.Flags.t, string) result
110-
=
128+
type flags = {driver_flags: Driver.Flags.t; color_output: bool}
129+
130+
(** Turn our array of flags into a Driver.Flags.t *)
131+
let process_flags (flags : 'a Js.opt) includes : (flags, string) result =
111132
let open Result in
112133
let open Common.Let_syntax.Result in
113134
let+ flags =
@@ -124,70 +145,77 @@ let process_flags (flags : 'a Js.opt) includes : (Driver.Flags.t, string) result
124145
Some ocaml_flags in
125146
match flags with
126147
| None ->
127-
{Driver.Flags.default with include_source= Include_files.InMemory includes}
148+
{ driver_flags=
149+
{ Driver.Flags.default with
150+
include_source= Include_files.InMemory includes }
151+
; color_output= false }
128152
| Some flags ->
129153
let is_flag_set flag = Array.mem ~equal:String.equal flags flag in
130154
let flag_val flag =
131155
let prefix = flag ^ "=" in
132156
Array.find_map flags ~f:(String.chop_prefix ~prefix) in
133-
{ optimization_level=
134-
(if is_flag_set "O0" then Optimize.O0
135-
else if is_flag_set "O1" || is_flag_set "O" then Optimize.O1
136-
else if is_flag_set "Oexperimental" then Optimize.Oexperimental
137-
else Optimize.O0)
138-
; allow_undefined= is_flag_set "allow-undefined"
139-
; functions_only= is_flag_set "functions-only"
140-
; standalone_functions= is_flag_set "standalone-functions"
141-
; use_opencl= is_flag_set "use-opencl"
142-
; include_source= Include_files.InMemory includes
143-
; info= is_flag_set "info"
144-
; version= is_flag_set "version"
145-
; auto_format= is_flag_set "auto-format" || is_flag_set "print-canonical"
146-
; debug_settings=
147-
{ print_ast= is_flag_set "debug-ast"
148-
; print_typed_ast= is_flag_set "debug-typed-ast"
149-
; print_mir=
150-
(if is_flag_set "debug-mir" then Basic
151-
else if is_flag_set "debug-mir-pretty" then Pretty
152-
else Off)
153-
; print_transformed_mir=
154-
(if is_flag_set "debug-transformed-mir" then Basic
155-
else if is_flag_set "debug-transformed-mir-pretty" then Pretty
156-
else Off)
157-
; print_optimized_mir=
158-
(if is_flag_set "debug-optimized-mir" then Basic
159-
else if is_flag_set "debug-optimized-mir-pretty" then Pretty
160-
else Off)
161-
; print_mem_patterns= is_flag_set "debug-mem-patterns"
162-
; force_soa= None
163-
; print_lir= is_flag_set "debug-lir"
164-
; debug_generate_data= is_flag_set "debug-generate-data"
165-
; debug_generate_inits= is_flag_set "debug-generate-inits"
166-
; debug_data_json= flag_val "debug-data-json" }
167-
; line_length=
168-
flag_val "max-line-length"
169-
|> Option.map ~f:int_of_string
170-
|> Option.value ~default:78
171-
; canonicalizer_settings=
172-
(if is_flag_set "print-canonical" then Canonicalize.legacy
173-
else
174-
match flag_val "canonicalize" with
175-
| None -> Canonicalize.none
176-
| Some s ->
177-
let parse settings s =
178-
match String.lowercase s with
179-
| "deprecations" ->
180-
Canonicalize.{settings with deprecations= true}
181-
| "parentheses" -> {settings with parentheses= true}
182-
| "braces" -> {settings with braces= true}
183-
| "strip-comments" -> {settings with strip_comments= true}
184-
| "includes" -> {settings with inline_includes= true}
185-
| _ -> settings in
186-
List.fold ~f:parse ~init:Canonicalize.none
187-
(String.split ~on:',' s))
188-
; warn_pedantic= is_flag_set "warn-pedantic"
189-
; warn_uninitialized= is_flag_set "warn-uninitialized"
190-
; filename_in_msg= flag_val "filename-in-msg" }
157+
{ driver_flags=
158+
{ optimization_level=
159+
(if is_flag_set "O0" then Optimize.O0
160+
else if is_flag_set "O1" || is_flag_set "O" then Optimize.O1
161+
else if is_flag_set "Oexperimental" then Optimize.Oexperimental
162+
else Optimize.O0)
163+
; allow_undefined= is_flag_set "allow-undefined"
164+
; functions_only= is_flag_set "functions-only"
165+
; standalone_functions= is_flag_set "standalone-functions"
166+
; use_opencl= is_flag_set "use-opencl"
167+
; include_source= Include_files.InMemory includes
168+
; info= is_flag_set "info"
169+
; version= is_flag_set "version"
170+
; auto_format=
171+
is_flag_set "auto-format" || is_flag_set "print-canonical"
172+
; debug_settings=
173+
{ print_ast= is_flag_set "debug-ast"
174+
; print_typed_ast= is_flag_set "debug-typed-ast"
175+
; print_mir=
176+
(if is_flag_set "debug-mir" then Basic
177+
else if is_flag_set "debug-mir-pretty" then Pretty
178+
else Off)
179+
; print_transformed_mir=
180+
(if is_flag_set "debug-transformed-mir" then Basic
181+
else if is_flag_set "debug-transformed-mir-pretty" then
182+
Pretty
183+
else Off)
184+
; print_optimized_mir=
185+
(if is_flag_set "debug-optimized-mir" then Basic
186+
else if is_flag_set "debug-optimized-mir-pretty" then Pretty
187+
else Off)
188+
; print_mem_patterns= is_flag_set "debug-mem-patterns"
189+
; force_soa= None
190+
; print_lir= is_flag_set "debug-lir"
191+
; debug_generate_data= is_flag_set "debug-generate-data"
192+
; debug_generate_inits= is_flag_set "debug-generate-inits"
193+
; debug_data_json= flag_val "debug-data-json" }
194+
; line_length=
195+
flag_val "max-line-length"
196+
|> Option.map ~f:int_of_string
197+
|> Option.value ~default:78
198+
; canonicalizer_settings=
199+
(if is_flag_set "print-canonical" then Canonicalize.legacy
200+
else
201+
match flag_val "canonicalize" with
202+
| None -> Canonicalize.none
203+
| Some s ->
204+
let parse settings s =
205+
match String.lowercase s with
206+
| "deprecations" ->
207+
Canonicalize.{settings with deprecations= true}
208+
| "parentheses" -> {settings with parentheses= true}
209+
| "braces" -> {settings with braces= true}
210+
| "strip-comments" -> {settings with strip_comments= true}
211+
| "includes" -> {settings with inline_includes= true}
212+
| _ -> settings in
213+
List.fold ~f:parse ~init:Canonicalize.none
214+
(String.split ~on:',' s))
215+
; warn_pedantic= is_flag_set "warn-pedantic"
216+
; warn_uninitialized= is_flag_set "warn-uninitialized"
217+
; filename_in_msg= flag_val "filename-in-msg" }
218+
; color_output= is_flag_set "color-output" }
191219

192220
(** Handle conversion of JS <-> OCaml values invoke driver *)
193221
let stan2cpp_wrapped name code flags includes =
@@ -196,18 +224,19 @@ let stan2cpp_wrapped name code flags includes =
196224
let compilation_result =
197225
let* name = checked_to_string ~name:"name" name in
198226
let* code = checked_to_string ~name:"code" code in
199-
let* driver_flags = process_flags flags includes in
227+
let* {driver_flags; color_output} = process_flags flags includes in
200228
let+ result, warnings =
201229
Common.ICE.with_exn_message (fun () ->
202230
invoke_driver name code driver_flags) in
203-
(result, warnings, driver_flags.filename_in_msg) in
231+
(result, warnings, driver_flags.filename_in_msg, color_output) in
204232
match compilation_result with
205-
| Ok (result, warnings, printed_filename) ->
233+
| Ok (result, warnings, printed_filename, color_output) ->
206234
let warnings =
207235
include_reader_warnings
208-
@ List.map ~f:(Fmt.str "%a" (Warnings.pp ?printed_filename)) warnings
209-
in
210-
wrap_result ?printed_filename ~code result ~warnings
236+
@ List.map
237+
~f:(str_color ~color_output "%a" (Warnings.pp ?printed_filename))
238+
warnings in
239+
wrap_result ?printed_filename ~color_output ~code result ~warnings
211240
| Error non_compilation_error (* either an ICE or malformed JS input *) ->
212241
wrap_error ~warnings:include_reader_warnings non_compilation_error
213242

test/stancjs/basic.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,7 @@ model {
2727

2828
let basic_err = stanc.stanc("basic_err", basic_model_err, []);
2929
utils.print_error(basic_err)
30+
31+
let basic_err_color = stanc.stanc("basic_err", basic_model_err, ["color-output"]);
32+
console.log("With ANSI color output:");
33+
utils.print_error(basic_err_color)

test/stancjs/stancjs.expected

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,18 @@ Semantic error in 'string', line 6, column 4 to column 5:
5959

6060
Identifier "z" not in scope.
6161

62+
With ANSI color output:
63+
Semantic error in 'string', line 6, column 4 to column 5:
64+
 -------------------------------------------------
65+
 4: }
66+
 5: model {
67+
 6: z ~ std_normal();
68+
^
69+
 7: }
70+
 -------------------------------------------------
71+

72+
Identifier "z" not in scope.
73+
6274
$ node canonical.js
6375
functions {
6476
int foo(int x) {

test/stancjs/warnings.js

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,6 @@ model {
1313
var deprecated_test = stanc.stanc("deprecated", deprecated_model);
1414
utils.print_warnings(deprecated_test)
1515

16-
var test_no_stderr_model = `
17-
transformed data {
18-
real p;
19-
p = p + 2.0;
20-
p <- 5.0;
21-
}
22-
parameters {
23-
real y;
24-
# hash comment is deprecated
25-
}
26-
model {
27-
y ~ normal(0,1);
28-
print(get_lp());
29-
increment_log_prob(5.0);
30-
}
31-
`
32-
var test_no_stderr = stanc.stanc("no_stderr", test_no_stderr_model, ["warn-uninitialized"]);
33-
3416

3517
var tc_warn = `
3618
parameters {

0 commit comments

Comments
 (0)