1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
open Ast

let parse_numeric_string s =
  let upper = String.uppercase_ascii (String.trim s) in
  match upper with
  | "TRUE" | "T" -> Some 1.0
  | "FALSE" | "F" -> Some 0.0
  | _ ->
      let s1 = Str.global_replace (Str.regexp_string "%") "" upper in
      let s2 = Str.global_replace (Str.regexp_string " ") "" s1 in
      let s3 = Str.global_replace (Str.regexp_string ",") "." s2 in
      let s4 = Str.global_replace (Str.regexp_string ";") "." s3 in
      try Some (float_of_string s4)
      with Failure _ -> None

(*
--# Convert to Integer
--#
--# Coerces a value to an integer robustly. Handles strings with
--# spaces, percentages, commas, and recognizes 'TRUE'/'FALSE'.
--#
--# @name to_integer
--# @param x :: Any The value to convert.
--# @return :: Int | NA The converted integer.
--# @example
--#   to_integer("12 300")
--#   to_integer("TRUE")
--#   to_integer(3.14)
--# @family core
--# @export
*)
let register_integer env =
  Env.add "to_integer"
    (make_builtin ~name:"to_integer" 1 (fun args _env ->
      let convert v = match v with
        | VInt i -> VInt i
        | VFloat f -> VInt (int_of_float f)
        | VBool b -> VInt (if b then 1 else 0)
        | VString s ->
            (match parse_numeric_string s with
             | Some f -> VInt (int_of_float f)
             | None -> (VNA NAGeneric))
        | VNA _ -> (VNA NAGeneric)
        | _ -> Error.type_error (Printf.sprintf "Cannot coerce %s to integer" (Utils.type_name v))
      in
      match args with
      | [VVector arr] ->
          let had_error = ref None in
          let res = Array.map (fun v -> 
            let converted = convert v in
            (match converted with VError _ as e -> had_error := Some e | _ -> ());
            converted
          ) arr in
          (match !had_error with Some e -> e | None -> VVector res)
      | [VList items] ->
          let had_error = ref None in
          let res = List.map (fun (n, v) -> 
            let converted = convert v in
            (match converted with VError _ as e -> had_error := Some e | _ -> ());
            (n, converted)
          ) items in
          (match !had_error with Some e -> e | None -> VList res)
      | [v] -> convert v
      | _ -> Error.arity_error_named "to_integer" 1 (List.length args)
    ))
    env

(*
--# Convert to Float
--#
--# Coerces a value to a float robustly. Handles strings with
--# spaces, percentages, commas, and recognizes 'TRUE'/'FALSE'.
--#
--# @name to_float
--# @param x :: Any The value to convert.
--# @return :: Float | NA The converted float.
--# @example
--#   to_float("3,14")
--#   to_float("15%")
--#   to_float(42)
--# @family core
--# @export
*)
let register_float env =
  Env.add "to_float"
    (make_builtin ~name:"to_float" 1 (fun args _env ->
      let convert v = match v with
        | VFloat f -> VFloat f
        | VInt i -> VFloat (float_of_int i)
        | VBool b -> VFloat (if b then 1.0 else 0.0)
        | VString s ->
            (match parse_numeric_string s with
             | Some f -> VFloat f
             | None -> (VNA NAGeneric))
        | VNA _ -> (VNA NAGeneric)
        | _ -> Error.type_error (Printf.sprintf "Cannot coerce %s to float" (Utils.type_name v))
      in
      match args with
      | [VVector arr] ->
          let had_error = ref None in
          let res = Array.map (fun v -> 
            let converted = convert v in
            (match converted with VError _ as e -> had_error := Some e | _ -> ());
            converted
          ) arr in
          (match !had_error with Some e -> e | None -> VVector res)
      | [VList items] ->
          let had_error = ref None in
          let res = List.map (fun (n, v) -> 
            let converted = convert v in
            (match converted with VError _ as e -> had_error := Some e | _ -> ());
            (n, converted)
          ) items in
          (match !had_error with Some e -> e | None -> VList res)
      | [v] -> convert v
      | _ -> Error.arity_error_named "to_float" 1 (List.length args)
    ))
    env

(*
--# Convert to Numeric
--#
--# Alias for `to_float`. Coerces a value to a numeric (float) robustly.
--#
--# @name to_numeric
--# @param x :: Any The value to convert.
--# @return :: Float | NA The converted float.
--# @family core
--# @export
*)
let register_numeric env =
  Env.add "to_numeric"
    (make_builtin ~name:"to_numeric" 1 (fun args _env ->
      let convert v = match v with
        | VFloat f -> VFloat f
        | VInt i -> VFloat (float_of_int i)
        | VBool b -> VFloat (if b then 1.0 else 0.0)
        | VString s ->
            (match parse_numeric_string s with
             | Some f -> VFloat f
             | None -> (VNA NAGeneric))
        | VNA _ -> (VNA NAGeneric)
        | _ -> Error.type_error (Printf.sprintf "Cannot coerce %s to numeric" (Utils.type_name v))
      in
      match args with
      | [VVector arr] ->
          let had_error = ref None in
          let res = Array.map (fun v -> 
            let converted = convert v in
            (match converted with VError _ as e -> had_error := Some e | _ -> ());
            converted
          ) arr in
          (match !had_error with Some e -> e | None -> VVector res)
      | [VList items] ->
          let had_error = ref None in
          let res = List.map (fun (n, v) -> 
            let converted = convert v in
            (match converted with VError _ as e -> had_error := Some e | _ -> ());
            (n, converted)
          ) items in
          (match !had_error with Some e -> e | None -> VList res)
      | [v] -> convert v
      | _ -> Error.arity_error_named "to_numeric" 1 (List.length args)
    ))
    env

(*
--# Convert a string to a Symbol
--#
--# Creates a Symbol from a string so it can be injected into quoted code with
--# `!!`. Existing Symbol values pass through unchanged.
--#
--# @name sym
--# @param x :: String | Symbol The name to convert.
--# @return :: Symbol The resulting symbol.
--# @example
--#   sym("mpg")
--#   expr(select(df, !!sym("mpg")))
--# @family core
--# @export
*)
let register_sym env =
  Env.add "sym"
    (make_builtin ~name:"sym" 1 (fun args _env ->
      match args with
      | [VString name] ->
          let trimmed = String.trim name in
          if trimmed = "" then
            Error.value_error "Function `sym` expects a non-empty String or Symbol."
          else
            VSymbol trimmed
      | [VSymbol name] -> VSymbol name
      | [_] -> Error.type_error "Function `sym` expects a String or Symbol."
      | _ -> Error.arity_error_named "sym" 1 (List.length args)
    ))
    env

let register env =
  env |> register_integer |> register_float |> register_numeric |> register_sym