diff --git a/docs/core_ideas.mld b/docs/core_ideas.mld new file mode 100644 index 0000000000..111cd474c0 --- /dev/null +++ b/docs/core_ideas.mld @@ -0,0 +1,71 @@ +{0 Core Ideas} + +This is not meant to be tutorial on the OCaml language itself, but rather on some of the +features and libraries of Stanc3 which may not be familiar to developers who do have +some OCaml exposure. + +{1 The Jane Street standard library} + +We use Jane Street's {{:https://ocaml.janestreet.com/ocaml-core/v0.14/doc/core_kernel/index.html}Core_kernel} +standard library. There are a few differences from the OCaml standard library which are instantly noticeable: + +{ul +{- Most higher-order functions such as [List.map] take a {i named} function argument. + + This means a call like [List.map square [1;2;3]] will look like [List.map ~f:square [1;2;3]].} + +{- Core_kernel defaults to safe operations which return {i options} rather than possibly erring. + + In "normal" OCaml, [List.hd] has type ['a list -> 'a]. + A call [List.hd []] will throw an exception. + By contrast, in the Jane Street libraries, the same function has type ['a list -> 'a option]. + + Usually, a function with the suffix [_exn] recovers the original signature, e.g. [List.hd_exn : 'a list -> 'a ].} +} + +If for some reason you {e need} functionality from the OCaml standard library that is not available in Jane Street +(be sure to triple check), you can use the modules [Stdlib] and [Caml] to access the built-in versions. Currently there +is only one such usage in the compiler, to use the standard definition of [!=] in the Menhir parser. + +There are a few other things we gain from these libraries. The most important idea to understand is {b deriving}. + +{2 Deriving functions} + +If you look at a type declaration of something like [Ast.typed_expression], you'll notice something curious after the declaration: + +{[ +type typed_expression = (typed_expr_meta, fun_kind) expr_with +[@@deriving sexp, compare, map, hash, fold] +]} + +When using an editor that supports code completion, you may notice that the [Ast] module suggests functions +which are not defined in the actual source text. This is because these functions are created at compile time by +{{:https://github.com/ocaml-ppx/ppx_deriving}[ppx_deriving]}.The above syntax [[@@deriving ...]] indicates which +functions we would like to be generated. + +These are very helpful - if a type derives [hash], it can automatically be used as keys in Jane Street's hash tables, and +a type which derives [sexp] can be serialized to Lisp-style S-Expressions. Most of our major types derive at least one function. + +{1 "Two Level Types"} + +The other curious thing about the types we define in our AST and MIR is that they use a trick known as "two level types". +This allows re-using the same type structure with different amounts of metadata attached (e.g., before type checking we have +an [untyped_program] which only has nodes and their source locations, after type checking we have [typed_program] which features +nodes, locations, {i and} types, in the same basic tree structure. + +The way that this is implemented is a bit non-obvious at first. Essentially, +many of the tree variant types are parameterized by something that ends up being a placeholder not +for just metadata but for the recursive type including metadata, +sometimes called the fixed point. +So instead of recursively referencing [expression], you would instead reference type parameter ['e], +which will later be filled in with something like [type expr_with_meta = metadata expression]. + + +This takes some getting used to, and also can lead to some unhelpful type signatures in editors such as +VSCode, because abbreviations are not always used in hover-over text. For example, [Expr.Typed.t], the MIR's typed +expression type, actually has a signature of [Expr.Typed.Meta.t Expr.Fixed.t]. + +{1 The [Fmt] library and pretty-printing} + +We extensively use the {{:https://erratique.ch/software/fmt}Fmt} library for our pretty-printing and code +generation. We have an existing guide on the {{:https://github.com/stan-dev/stanc3/wiki/Format---Fmt-code-generation-quickstart}wiki} which covers the core ideas. diff --git a/docs/getting_started.mld b/docs/getting_started.mld index f2ecc4e954..b7d330ccf8 100644 --- a/docs/getting_started.mld +++ b/docs/getting_started.mld @@ -156,3 +156,9 @@ If you use {{:https://github.com/jwiegley/use-package}[use-package.el]}, this sn (define-key tuareg-mode-map (kbd "C-M-") #'ocamlformat) (add-hook 'before-save-hook #'ocamlformat-before-save))) ]} + +{2 Finding your way around the code} + +Even to an experienced OCaml developer, there are certain practices +in the stanc3 codebase which might be unfamiliar. We have tried to document +those on our {{!page-core_ideas}Core ideas} page. diff --git a/docs/index.mld b/docs/index.mld index 16cfe7140e..5d0bdcd2ec 100644 --- a/docs/index.mld +++ b/docs/index.mld @@ -15,12 +15,20 @@ See {{!page-getting_started}the Getting Started page} for information on setting Here are some resources (internal and external) which may be useful to a new developer. +- {{:https://dev.realworldocaml.org/} Real World OCaml}, a free introduction to the OCaml language which features some + of the libraries we use. + +- {{!page-core_ideas} An overview of some elements of Stanc which may be strange to existing OCaml developers}. + - {{:https://github.com/stan-dev/stanc3/wiki/Format---Fmt-code-generation-quickstart} Wiki on using [Format]/[Fmt] module} + - {{!page-parser_messages} Page on modifying the parser's error messages} + - {{!page-exposing_new_functions} Page on exposing a new function of the Stan Math library} + - {{:https://github.com/stan-dev/stanc3/wiki/Software-Engineering-best-practices} Wiki on generic software best practices} diff --git a/src/analysis_and_optimization/Mem_pattern.ml b/src/analysis_and_optimization/Mem_pattern.ml index a66f8f3279..a82c108ea0 100644 --- a/src/analysis_and_optimization/Mem_pattern.ml +++ b/src/analysis_and_optimization/Mem_pattern.ml @@ -3,8 +3,8 @@ open Core_kernel.Poly open Middle (** - * Return a Var expression of the name for each type - * containing an eigen matrix + Return a Var expression of the name for each type + containing an eigen matrix *) let rec matrix_set Expr.Fixed.{pattern; meta= Expr.Typed.Meta.{type_; _} as meta} = @@ -22,8 +22,8 @@ let rec matrix_set Expr.Fixed.{pattern; meta= Expr.Typed.Meta.{type_; _} as meta else Set.Poly.empty (** - * Return a set of all types containing autodiffable Eigen matrices - * in an expression. + Return a set of all types containing autodiffable Eigen matrices + in an expression. *) let query_var_eigen_names (expr : Expr.Typed.t) : string Set.Poly.t = let get_expr_eigen_names @@ -36,7 +36,7 @@ let query_var_eigen_names (expr : Expr.Typed.t) : string Set.Poly.t = Set.Poly.filter_map ~f:get_expr_eigen_names (matrix_set expr) (** - * Check whether one set is a nonzero subset of another set. + Check whether one set is a nonzero subset of another set. *) let is_nonzero_subset ~set ~subset = Set.Poly.is_subset subset ~of_:set @@ -44,9 +44,9 @@ let is_nonzero_subset ~set ~subset = && not (Set.Poly.is_empty subset) (** - * Check an expression to count how many times we see a single index. - * @param acc An accumulator from previous folds of multiple expressions. - * @param pattern The expression patterns to match against + Check an expression to count how many times we see a single index. + @param acc An accumulator from previous folds of multiple expressions. + @param pattern The expression patterns to match against *) let rec count_single_idx_exprs (acc : int) Expr.Fixed.{pattern; _} : int = match pattern with @@ -70,12 +70,12 @@ let rec count_single_idx_exprs (acc : int) Expr.Fixed.{pattern; _} : int = acc (** - * Check an Index to count how many times we see a single index. - * @param acc An accumulator from previous folds of multiple expressions. - * @param idx An Index to match. For Single types this adds 1 to the - * acc. For Upfrom and MultiIndex types we check the inner expression - * for a Single index. All and Between cannot be Single cell access - * and so pass acc along. + Check an Index to count how many times we see a single index. + @param acc An accumulator from previous folds of multiple expressions. + @param idx An Index to match. For Single types this adds 1 to the + acc. For Upfrom and MultiIndex types we check the inner expression + for a Single index. All and Between cannot be Single cell access + and so pass acc along. *) and count_single_idx (acc : int) (idx : Expr.Typed.t Index.t) = match idx with @@ -83,14 +83,14 @@ and count_single_idx (acc : int) (idx : Expr.Typed.t Index.t) = | Single _ -> acc + 1 (** - * Find indices on Matrix and Vector types that perform single - * cell access. Returns true if it finds - * a vector, row vector, matrix, or matrix with single cell access - * as well as an array of any of the above that is accessing the - * inner matrix types cell. - * @param ut An UnsizedType to match against. - * @param index This list is checked for Single cell access - * either at the top level or within the `Index` types of the list. + Find indices on Matrix and Vector types that perform single + cell access. Returns true if it finds + a vector, row vector, matrix, or matrix with single cell access + as well as an array of any of the above that is accessing the + inner matrix types cell. + @param ut An UnsizedType to match against. + @param index This list is checked for Single cell access + either at the top level or within the [Index] types of the list. *) let rec is_uni_eigen_loop_indexing in_loop (ut : UnsizedType.t) (index : Expr.Typed.t Index.t list) = @@ -138,14 +138,14 @@ let is_fun_soa_supported name exprs = query_stan_math_mem_pattern_support name fun_args (** - * Query to find the initial set of objects that cannot be SoA. - * This is mostly recursing over expressions, with the exceptions - * being functions and indexing expressions. For the logic on functions - * see the docs for `query_initial_demotable_funs`. - * @param in_loop a boolean to signify if the expression exists inside - * of a loop. If so, the names of matrix and vector like objects - * will be returned if the matrix or vector is accessed by single - * cell indexing. + Query to find the initial set of objects that cannot be SoA. + This is mostly recursing over expressions, with the exceptions + being functions and indexing expressions. For the logic on functions + see the docs for [query_initial_demotable_funs]. + @param in_loop a boolean to signify if the expression exists inside + of a loop. If so, the names of matrix and vector like objects + will be returned if the matrix or vector is accessed by single + cell indexing. *) let rec query_initial_demotable_expr (in_loop : bool) ~(acc : string Set.Poly.t) Expr.Fixed.{pattern; _} : string Set.Poly.t = @@ -182,21 +182,21 @@ let rec query_initial_demotable_expr (in_loop : bool) ~(acc : string Set.Poly.t) Set.Poly.union (query_expr full_lhs_rhs lhs) (query_expr full_lhs_rhs rhs) (** - * Query a function to detect if it or any of its used - * expression's objects or expressions should be demoted to AoS. + Query a function to detect if it or any of its used + expression's objects or expressions should be demoted to AoS. * - * The logic here demotes the expressions in a function to AoS if - * the function's inner expression returns has a meta type containing a matrix - * and either of : - * (1) The function is user defined and the UDFs inputs are matrices. - * (2) The Stan math function cannot support AoS - * @param in_loop A boolean to specify the logic of indexing expressions. See - * `query_initial_demotable_expr` for an explanation of the logic. - * @param kind The function type, for StanLib functions we check if the - * function supports SoA and for UserDefined functions we always fail - * and return back all of the names of the objects passed in expressions - * to the UDF. - * exprs The expression list passed to the functions. + The logic here demotes the expressions in a function to AoS if + the function's inner expression returns has a meta type containing a matrix + and either of : + (1) The function is user defined and the UDFs inputs are matrices. + (2) The Stan math function cannot support AoS + @param in_loop A boolean to specify the logic of indexing expressions. See + [query_initial_demotable_expr] for an explanation of the logic. + @param kind The function type, for StanLib functions we check if the + function supports SoA and for UserDefined functions we always fail + and return back all of the names of the objects passed in expressions + to the UDF. + exprs The expression list passed to the functions. *) and query_initial_demotable_funs (in_loop : bool) (acc : string Set.Poly.t) (kind : 'a Fun_kind.t) (exprs : Expr.Typed.t list) : string Set.Poly.t = @@ -221,8 +221,8 @@ and query_initial_demotable_funs (in_loop : bool) (acc : string Set.Poly.t) Set.Poly.union acc demoted_and_top_level_names (** - * Check whether any functions in the right hand side expression of an assignment - * support SoA. If so then return true, otherwise return false. + Check whether any functions in the right hand side expression of an assignment + support SoA. If so then return true, otherwise return false. *) let rec is_any_soa_supported_expr Expr.Fixed.{pattern; meta= Expr.Typed.Meta.{adlevel; type_; _}} : bool = @@ -245,7 +245,7 @@ let rec is_any_soa_supported_expr is_any_soa_supported_expr lhs && is_any_soa_supported_expr rhs (** - * Return false if the `Fun_kind.t` does not support `SoA` + Return false if the [Fun_kind.t] does not support [SoA] *) and is_any_soa_supported_fun_expr (kind : 'a Fun_kind.t) (exprs : Expr.Typed.t list) : bool = @@ -261,8 +261,8 @@ and is_any_soa_supported_fun_expr (kind : 'a Fun_kind.t) && List.exists ~f:is_any_soa_supported_expr exprs ) (** - * Return true if the rhs expression of an assignment contains only - * combinations of AutoDiffable Reals and Data Matrices + Return true if the rhs expression of an assignment contains only + combinations of AutoDiffable Reals and Data Matrices *) let rec is_any_ad_real_data_matrix_expr Expr.Fixed.{pattern; meta= Expr.Typed.Meta.{adlevel; _}} : bool = @@ -283,8 +283,8 @@ let rec is_any_ad_real_data_matrix_expr && is_any_ad_real_data_matrix_expr rhs (** - * Return true if the expressions in a function call are all - * combinations of AutoDiffable Reals and Data Matrices + Return true if the expressions in a function call are all + combinations of AutoDiffable Reals and Data Matrices *) and is_any_ad_real_data_matrix_expr_fun (kind : 'a Fun_kind.t) (exprs : Expr.Typed.t list) : bool = @@ -326,28 +326,28 @@ and is_any_ad_real_data_matrix_expr_fun (kind : 'a Fun_kind.t) | UserDefined ((_ : string), (_ : bool Fun_kind.suffix)) -> false (** - * Query to find the initial set of objects in statements that cannot be SoA. - * This is mostly recursive over expressions and statements, with the exception of - * functions and Assignments. + Query to find the initial set of objects in statements that cannot be SoA. + This is mostly recursive over expressions and statements, with the exception of + functions and Assignments. * - * For assignments: - * We demote the LHS variable if any of the following are true: - * 1. None of the RHS's functions are able to accept SoA matrices - * and the rhs is not an internal compiler function. - * 2. A single cell of the LHS is being assigned within a loop. - * 3. The top level expression on the RHS is a combination of only - * data matrices and scalar types. Operations on data matrix and - * scalar values in Stan math will return a AoS matrix. We currently - * have no way to tell Stan math to return a SoA matrix. + For assignments: + We demote the LHS variable if any of the following are true: + 1. None of the RHS's functions are able to accept SoA matrices + and the rhs is not an internal compiler function. + 2. A single cell of the LHS is being assigned within a loop. + 3. The top level expression on the RHS is a combination of only + data matrices and scalar types. Operations on data matrix and + scalar values in Stan math will return a AoS matrix. We currently + have no way to tell Stan math to return a SoA matrix. * - * We demote RHS variables if any of the following are true: - * 1. The LHS variable has previously or through this iteration - * been marked AoS. + We demote RHS variables if any of the following are true: + 1. The LHS variable has previously or through this iteration + been marked AoS. * - * For functions see the documentation for `query_initial_demotable_funs` for - * the logic on demotion rules. - * @param in_loop A boolean to specify the logic of indexing expressions. See - * `query_initial_demotable_expr` for an explanation of the logic. + For functions see the documentation for [query_initial_demotable_funs] for + the logic on demotion rules. + @param in_loop A boolean to specify the logic of indexing expressions. See + [query_initial_demotable_expr] for an explanation of the logic. *) let rec query_initial_demotable_stmt (in_loop : bool) (acc : string Set.Poly.t) (Stmt.Fixed.{pattern; _} : Stmt.Located.t) : string Set.Poly.t = @@ -428,16 +428,16 @@ let rec query_initial_demotable_stmt (in_loop : bool) (acc : string Set.Poly.t) | Skip | Break | Continue | Decl _ -> acc (** Look through a statement to see whether the objects used in it need to be - * modified from SoA to AoS. Returns the set of object names that need demoted - * in a statement, if any. - * This function looks at Assignment statements, and returns back the - * set of top level object names given: - * 1. If the name of the lhs assignee is in the `aos_exits`, all the names - * of the expressions with a type containing a matrix are returned. - * 2. If the names of the rhs objects containing matrix types are in the subset of - * aos_exits. - * @param aos_exits A set of variables that can be demoted. - * @param pattern The Stmt pattern to query. + modified from SoA to AoS. Returns the set of object names that need demoted + in a statement, if any. + This function looks at Assignment statements, and returns back the + set of top level object names given: + 1. If the name of the lhs assignee is in the [aos_exits], all the names + of the expressions with a type containing a matrix are returned. + 2. If the names of the rhs objects containing matrix types are in the subset of + aos_exits. + @param aos_exits A set of variables that can be demoted. + @param pattern The Stmt pattern to query. *) let query_demotable_stmt (aos_exits : string Set.Poly.t) (pattern : (Expr.Typed.t, int) Stmt.Fixed.Pattern.t) : string Set.Poly.t = @@ -458,19 +458,19 @@ let query_demotable_stmt (aos_exits : string Set.Poly.t) | _ -> Set.Poly.empty (** - * Modify a function and it's subexpressions from SoA <-> AoS and vice versa. - * This performs demotion for sub expressions recursively. The top level - * expression and it's sub expressions are demoted to SoA if - * 1. The names of the variables in the subexpressions returning - * objects holding matrices are all in the modifiable set. - * 2. The function does not support SoA - * 3. The `force` argument is `true` - * @param force_demotion If true, forces an expression and it's sub-expressions - * to be AoS. - * @param modifiable_set The set of names that are either demotable - * to AoS or promotable to SoA. - * @param kind A `Fun_kind.t` - * @param exprs A list of expressions going into the function. + Modify a function and it's subexpressions from SoA <-> AoS and vice versa. + This performs demotion for sub expressions recursively. The top level + expression and it's sub expressions are demoted to SoA if + 1. The names of the variables in the subexpressions returning + objects holding matrices are all in the modifiable set. + 2. The function does not support SoA + 3. The [force] argument is [true] + @param force_demotion If true, forces an expression and it's sub-expressions + to be AoS. + @param modifiable_set The set of names that are either demotable + to AoS or promotable to SoA. + @param kind A [Fun_kind.t] + @param exprs A list of expressions going into the function. **) let rec modify_kind ?force_demotion:(force = false) (modifiable_set : string Set.Poly.t) (kind : 'a Fun_kind.t) @@ -497,18 +497,18 @@ let rec modify_kind ?force_demotion:(force = false) , List.map ~f:(modify_expr ~force_demotion:force modifiable_set) exprs ) (** - * Modify an expression and it's subexpressions from SoA <-> AoS - * and vice versa. The only real paths in the below is on the - * functions and ternary expressions. + Modify an expression and it's subexpressions from SoA <-> AoS + and vice versa. The only real paths in the below is on the + functions and ternary expressions. * - * The logic for functions is defined in `modify_kind`. - * `TernaryIf` is forcefully demoted to AoS if the type of the expression - * contains a matrix. - * @param force_demotion If true, forces an expression and it's sub-expressions - * to be AoS. - * @param modifiable_set The name of the variables whose - * associated expressions we want to modify. - * @param pattern The expression to modify. + The logic for functions is defined in [modify_kind]. + [TernaryIf] is forcefully demoted to AoS if the type of the expression + contains a matrix. + @param force_demotion If true, forces an expression and it's sub-expressions + to be AoS. + @param modifiable_set The name of the variables whose + associated expressions we want to modify. + @param pattern The expression to modify. *) and modify_expr_pattern ?force_demotion:(force = false) (modifiable_set : string Set.Poly.t) @@ -546,12 +546,12 @@ and modify_expr_pattern ?force_demotion:(force = false) pattern (** -* Given a Set of strings containing the names of objects that can be -* modified from AoS <-> SoA and vice versa, modify them within the expression. -* @param mem_pattern The memory pattern to change expressions to. -* @param modifiable_set The name of the variables whose -* associated expressions we want to modify. -* @param expr the expression to modify. + Given a Set of strings containing the names of objects that can be + modified from AoS <-> SoA and vice versa, modify them within the expression. + @param mem_pattern The memory pattern to change expressions to. + @param modifiable_set The name of the variables whose + associated expressions we want to modify. + @param expr the expression to modify. *) and modify_expr ?force_demotion:(force = false) (modifiable_set : string Set.Poly.t) (Expr.Fixed.{pattern; _} as expr) = @@ -559,16 +559,16 @@ and modify_expr ?force_demotion:(force = false) pattern= modify_expr_pattern ~force_demotion:force modifiable_set pattern } (** -* Modify statement patterns in the MIR from AoS <-> SoA and vice versa -* For `Decl` and `Assignment`'s reading in parameters, we demote to AoS -* if the `decl_id` (or assign name) is in the modifiable set and -* otherwise promote the statement to `SoA`. -* For general `Assignment` statements, we check if the assignee is in -* the demotable set. If so, we force demotion of all of the rhs expressions. -* All other statements recurse over their statements and expressions. + Modify statement patterns in the MIR from AoS <-> SoA and vice versa + For [Decl] and [Assignment]'s reading in parameters, we demote to AoS + if the [decl_id] (or assign name) is in the modifiable set and + otherwise promote the statement to [SoA]. + For general [Assignment] statements, we check if the assignee is in + the demotable set. If so, we force demotion of all of the rhs expressions. + All other statements recurse over their statements and expressions. * -* @param pattern The statement pattern to modify -* @param modifiable_set The name of the variable we are searching for. + @param pattern The statement pattern to modify + @param modifiable_set The name of the variable we are searching for. *) let rec modify_stmt_pattern (pattern : (Expr.Typed.t, Stmt.Located.t) Stmt.Fixed.Pattern.t) @@ -645,12 +645,12 @@ let rec modify_stmt_pattern | Skip | Break | Continue | Decl _ -> pattern (** -* Modify statement patterns in the MIR from AoS <-> SoA and vice versa -* @param mem_pattern A mem_pattern to modify expressions to. For the -* given memory pattern, this modifies -* statement patterns and expressions to it. -* @param stmt The statement to modify. -* @param modifiable_set The name of the variable we are searching for. + Modify statement patterns in the MIR from AoS <-> SoA and vice versa + @param mem_pattern A mem_pattern to modify expressions to. For the + given memory pattern, this modifies + statement patterns and expressions to it. + @param stmt The statement to modify. + @param modifiable_set The name of the variable we are searching for. *) and modify_stmt (Stmt.Fixed.{pattern; _} as stmt) (modifiable_set : string Set.Poly.t) = diff --git a/src/analysis_and_optimization/Monotone_framework.ml b/src/analysis_and_optimization/Monotone_framework.ml index f0e03238ed..7d4c073815 100644 --- a/src/analysis_and_optimization/Monotone_framework.ml +++ b/src/analysis_and_optimization/Monotone_framework.ml @@ -134,14 +134,14 @@ let reverse (type l) (module F : FLOWGRAPH with type labels = l) = with type labels = l ) (** Modify the end nodes of a flowgraph to depend on its inits - * To force the monotone framework to run until the program never changes - * this function modifies the input `Flowgraph` so that it's end nodes - * depend on it's initial nodes. The inits of the reverse flowgraph are used - * for this since we normally have both the forward and reverse flowgraphs - * available. - * @tparam l Type of the label for each flowgraph, most commonly an int - * @param Flowgraph The flowgraph to modify - * @param RevFlowgraph The same flowgraph as `Flowgraph` but reversed. + To force the monotone framework to run until the program never changes + this function modifies the input [Flowgraph] so that it's end nodes + depend on it's initial nodes. The inits of the reverse flowgraph are used + for this since we normally have both the forward and reverse flowgraphs + available. + @param l Type of the label for each flowgraph, most commonly an int + @param Flowgraph The flowgraph to modify + @param RevFlowgraph The same flowgraph as [Flowgraph] but reversed. * *) let make_circular_flowgraph (type l) @@ -1016,12 +1016,12 @@ let lazy_expressions_mfp (latest_expr, used_not_latest_expressions_mfp) (** Run the minimal fixed point algorithm to deduce the smallest set of - * variables that satisfy a set of conditions. - * @param Flowgraph The set of nodes to analyze - * @param flowgraph_to_mir Map of nodes to their actual values in the MIR - * @param initial_variables The set of variables to start in the set - * @param gen_variable Used in the transfer function to deduce variables - * that should be in the set + variables that satisfy a set of conditions. + @param Flowgraph The set of nodes to analyze + @param flowgraph_to_mir Map of nodes to their actual values in the MIR + @param initial_variables The set of variables to start in the set + @param gen_variable Used in the transfer function to deduce variables + that should be in the set * *) let minimal_variables_mfp diff --git a/src/stan_math_backend/Stan_math_code_gen.ml b/src/stan_math_backend/Stan_math_code_gen.ml index ede0fe0690..471dd19850 100644 --- a/src/stan_math_backend/Stan_math_code_gen.ml +++ b/src/stan_math_backend/Stan_math_code_gen.ml @@ -10,8 +10,8 @@ was able to make a decent-looking pretty-printer for a subset of Javascript[3] that might serve as a good reference. Good luck! - [0] Format module doc: https://caml.inria.fr/pub/docs/manual-ocaml/libref/Format.html - [1] Fmt module doc: https://erratique.ch/software/fmt/doc/Fmt.html + [0] Format module doc: https://ocaml.org/api/Format.html + [1] Fmt module doc: https://erratique.ch/software/fmt/doc/Fmt/index.html [2] Format Unraveled: https://hal.archives-ouvertes.fr/hal-01503081/file/format-unraveled.pdf [3] Javascript pretty-printer https://github.com/Virum/compiler/blob/28e807b842bab5dcf11460c8193dd5b16674951f/JavaScript.ml#L112 *)