mirror of https://github.com/nealey/irc-bot
404 lines
9.0 KiB
OCaml
404 lines
9.0 KiB
OCaml
|
(* Conversions between numbers and strings. *)
|
||
|
|
||
|
open Ocs_types
|
||
|
open Ocs_error
|
||
|
open Ocs_numaux
|
||
|
open Ocs_num
|
||
|
open Ocs_env
|
||
|
|
||
|
open Num
|
||
|
open Big_int
|
||
|
open Ratio
|
||
|
|
||
|
(* We need to scan strings and keep track of our position. *)
|
||
|
|
||
|
type sbuf = {
|
||
|
s_str : string;
|
||
|
mutable s_pos : int
|
||
|
}
|
||
|
|
||
|
let speek s =
|
||
|
if s.s_pos < String.length s.s_str then
|
||
|
Some s.s_str.[s.s_pos]
|
||
|
else
|
||
|
None
|
||
|
;;
|
||
|
|
||
|
let skip s =
|
||
|
s.s_pos <- s.s_pos + 1
|
||
|
;;
|
||
|
|
||
|
let sget s =
|
||
|
match speek s with
|
||
|
(Some _) as c -> skip s; c
|
||
|
| _ -> None
|
||
|
;;
|
||
|
|
||
|
let ssleft s =
|
||
|
(String.length s.s_str) - s.s_pos
|
||
|
;;
|
||
|
|
||
|
(* Converting strings to numbers is fairly complex. We have a lot of
|
||
|
cases to consider. *)
|
||
|
|
||
|
type exactness =
|
||
|
Exact
|
||
|
| Inexact
|
||
|
| Undef
|
||
|
|
||
|
let parse_prefix s =
|
||
|
let rec nextp b e =
|
||
|
match speek s with
|
||
|
Some '#' ->
|
||
|
begin
|
||
|
skip s;
|
||
|
match sget s with
|
||
|
Some ('E' | 'e') ->
|
||
|
if e <> Undef then raise (Error "invalid #e") else nextp b Exact
|
||
|
| Some ('I' | 'i') ->
|
||
|
if e <> Undef then raise (Error "invalid #i") else nextp b Inexact
|
||
|
| Some ('B' | 'b') ->
|
||
|
if b <> 0 then raise (Error "invalid #b") else nextp 2 e
|
||
|
| Some ('O' | 'o') ->
|
||
|
if b <> 0 then raise (Error "invalid #o") else nextp 8 e
|
||
|
| Some ('D' | 'd') ->
|
||
|
if b <> 0 then raise (Error "invalid #d") else nextp 10 e
|
||
|
| Some ('X' | 'x') ->
|
||
|
if b <> 0 then raise (Error "invalid #x") else nextp 16 e
|
||
|
| _ -> raise (Error "invalid prefix")
|
||
|
end
|
||
|
| _ -> (b, e)
|
||
|
in
|
||
|
nextp 0 Undef
|
||
|
;;
|
||
|
|
||
|
let strtobi s base =
|
||
|
let n = String.length s
|
||
|
and am v i = add_int_big_int i (mult_int_big_int base v) in
|
||
|
let rec loop i v =
|
||
|
if i >= n then
|
||
|
v
|
||
|
else
|
||
|
loop (i + 1) (am v
|
||
|
(match s.[i] with
|
||
|
'0' .. '9' as c -> int_of_char c - int_of_char '0'
|
||
|
| 'a' .. 'f' as c -> int_of_char c - int_of_char 'a' + 10
|
||
|
| 'A' .. 'F' as c -> int_of_char c - int_of_char 'A' + 10
|
||
|
| _ -> raise (Error "invalid number")))
|
||
|
in
|
||
|
loop 0 (big_int_of_int 0)
|
||
|
;;
|
||
|
|
||
|
let read_bigint s base =
|
||
|
if base = 10 then
|
||
|
big_int_of_string s
|
||
|
else
|
||
|
strtobi s base
|
||
|
;;
|
||
|
|
||
|
(* The largest integer is at *least* this big (bigger on 64-bit machines). *)
|
||
|
let max_int = 0x3fffffff
|
||
|
let min_int = -max_int - 1
|
||
|
|
||
|
let parse_num s base =
|
||
|
let fixsign =
|
||
|
match speek s with
|
||
|
Some '-' -> skip s; negate
|
||
|
| Some '+' -> skip s; fun x -> x
|
||
|
| _ -> fun x -> x
|
||
|
and maxv = max_int / base
|
||
|
and maxi = max_int mod base
|
||
|
in
|
||
|
let rec scann v o =
|
||
|
match speek s with
|
||
|
Some ('0' .. '9' as c)
|
||
|
when (int_of_char c) - (int_of_char '0') < base ->
|
||
|
addo v ((int_of_char c) - (int_of_char '0')) o
|
||
|
| Some ('a' .. 'f' as c) when base = 16 ->
|
||
|
addo v ((int_of_char c) - (int_of_char 'a') + 10) o
|
||
|
| Some ('A' .. 'F' as c) when base = 16 ->
|
||
|
addo v ((int_of_char c) - (int_of_char 'A') + 10) o
|
||
|
| _ -> (v, o)
|
||
|
and addo v i o =
|
||
|
skip s;
|
||
|
if o || v > maxv || (v = maxv && i > maxi) then
|
||
|
scann 0 true
|
||
|
else
|
||
|
scann (v * base + i) o
|
||
|
and readn () =
|
||
|
let sp = s.s_pos in
|
||
|
match scann 0 false with
|
||
|
(i, false) ->
|
||
|
if s.s_pos = sp then
|
||
|
raise (Error "invalid number")
|
||
|
else
|
||
|
Sint i
|
||
|
| (_, true) ->
|
||
|
Sbigint (read_bigint (String.sub s.s_str sp (s.s_pos - sp)) base)
|
||
|
in
|
||
|
let num = readn () in
|
||
|
match speek s with
|
||
|
Some '/' ->
|
||
|
skip s;
|
||
|
fixsign (div2 (Sbigint (bigint_of_snum num))
|
||
|
(Sbigint (bigint_of_snum (readn ()))))
|
||
|
| Some ('+' | '-' | '@') | None -> fixsign num
|
||
|
| _ -> raise (Error "invalid rational")
|
||
|
;;
|
||
|
|
||
|
let parse_flo10 s =
|
||
|
let sp = s.s_pos in
|
||
|
let rec skipd isfirst =
|
||
|
match speek s with
|
||
|
Some '0' .. '9' | Some '#' -> skip s; skipd false
|
||
|
| Some ('+' | '-') when isfirst -> skip s; skipd false
|
||
|
| _ -> ()
|
||
|
in
|
||
|
skipd true;
|
||
|
if speek s = Some '.' then
|
||
|
begin
|
||
|
skip s;
|
||
|
skipd false
|
||
|
end;
|
||
|
begin
|
||
|
match speek s with
|
||
|
Some ('E' | 'e' | 'F' | 'f' | 'D' | 'd' | 'S' | 's' | 'L' | 'l') ->
|
||
|
skip s; skipd true
|
||
|
| _ -> ()
|
||
|
end;
|
||
|
let t = String.sub s.s_str sp (s.s_pos - sp) in
|
||
|
for i = 0 to String.length t - 1 do
|
||
|
match t.[i] with
|
||
|
'#' -> t.[i] <- '0'
|
||
|
| 'F' | 'f' | 'D' | 'd' | 'S' | 's' | 'L' | 'l' -> t.[i] <- 'e'
|
||
|
| _ -> ()
|
||
|
done;
|
||
|
try
|
||
|
Sreal (float_of_string t)
|
||
|
with
|
||
|
Failure _ -> raise (Error "invalid float")
|
||
|
;;
|
||
|
|
||
|
let string_to_num str ub =
|
||
|
(* Special cases for [-+][iI] *)
|
||
|
if str = "+i" || str = "+I" then
|
||
|
Scomplex { Complex.re = 0.0; Complex.im = 1.0; }
|
||
|
else if str = "-i" || str = "-I" then
|
||
|
Scomplex { Complex.re = 0.0; Complex.im = -1.0; }
|
||
|
else
|
||
|
let s = { s_str = str; s_pos = 0 } in
|
||
|
let (base, ex) =
|
||
|
match parse_prefix s with
|
||
|
0, x -> if ub = 0 then (10, x) else (ub, x)
|
||
|
| (b, x) as r ->
|
||
|
if ub <> 0 && ub <> b then
|
||
|
raise (Error "Base mismatch")
|
||
|
else r
|
||
|
in
|
||
|
let getn () =
|
||
|
if base = 10 && ex <> Exact then
|
||
|
begin
|
||
|
let sp = s.s_pos in
|
||
|
try
|
||
|
parse_num s 10
|
||
|
with _ ->
|
||
|
s.s_pos <- sp;
|
||
|
parse_flo10 s
|
||
|
end
|
||
|
else
|
||
|
parse_num s base
|
||
|
and fixex n =
|
||
|
match (ex, n) with
|
||
|
(Inexact, (Sint _ | Sbigint _ | Srational _)) -> promote_real n
|
||
|
| (Exact, (Sreal _ | Scomplex _)) -> raise (Error "Not exact")
|
||
|
| _ -> n
|
||
|
in
|
||
|
let a = fixex (getn ()) in
|
||
|
match speek s with
|
||
|
Some ('+' | '-' as c) ->
|
||
|
if ex = Exact then
|
||
|
raise (Error "Complex not exact")
|
||
|
else
|
||
|
if ssleft s = 2 && s.s_str.[s.s_pos + 1] = 'i' then
|
||
|
Scomplex { Complex.re = float_of_snum a;
|
||
|
Complex.im = (if c = '-' then -1.0 else 1.0) }
|
||
|
else
|
||
|
let b = getn () in
|
||
|
if ssleft s <> 1 || speek s <> Some 'i' then
|
||
|
raise (Error "invalid number")
|
||
|
else
|
||
|
Scomplex { Complex.re = float_of_snum a;
|
||
|
Complex.im = float_of_snum b }
|
||
|
| Some '@' ->
|
||
|
skip s;
|
||
|
if ex = Exact then
|
||
|
raise (Error "Complex not exact")
|
||
|
else
|
||
|
let b = getn () in
|
||
|
if ssleft s <> 0 then
|
||
|
raise (Error "invalid number")
|
||
|
else
|
||
|
let r = float_of_snum a
|
||
|
and t = float_of_snum b in
|
||
|
Scomplex (Complex.polar r t)
|
||
|
| Some c -> raise (Error "invalid number")
|
||
|
| None -> a
|
||
|
;;
|
||
|
|
||
|
let snum_strtonum av =
|
||
|
match Array.length av with
|
||
|
(1 | 2) as n ->
|
||
|
let r =
|
||
|
if n = 2 then
|
||
|
begin
|
||
|
match av.(1) with
|
||
|
Sint i -> i
|
||
|
| _ -> raise (Error "string->number: invalid radix")
|
||
|
end
|
||
|
else
|
||
|
0
|
||
|
in
|
||
|
begin
|
||
|
match av.(0) with
|
||
|
Sstring s ->
|
||
|
begin
|
||
|
try
|
||
|
if s = "" then
|
||
|
Sfalse
|
||
|
else
|
||
|
string_to_num s r
|
||
|
with
|
||
|
_ -> Sfalse
|
||
|
end
|
||
|
| _ -> raise (Error "string->number: not a string")
|
||
|
end
|
||
|
| _ -> raise (Error "string->number: wrong number of args")
|
||
|
;;
|
||
|
|
||
|
let string_of_real_s r =
|
||
|
let rec loop n =
|
||
|
let s = Printf.sprintf "%.*g" n r in
|
||
|
if n >= 25 || r = float_of_string s then s
|
||
|
else loop (n + 1)
|
||
|
in
|
||
|
loop 14
|
||
|
;;
|
||
|
|
||
|
let string_of_real r =
|
||
|
let s = string_of_real_s r in
|
||
|
let n = String.length s in
|
||
|
let rec loop i =
|
||
|
if i >= n then s ^ ".0"
|
||
|
else if s.[i] = '.' || s.[i] = 'e' then s
|
||
|
else loop (i + 1)
|
||
|
in
|
||
|
loop 0
|
||
|
;;
|
||
|
|
||
|
let string_of_complex =
|
||
|
function { Complex.re = r; Complex.im = i } ->
|
||
|
(string_of_real_s r) ^
|
||
|
(if i < 0.0 then
|
||
|
begin
|
||
|
if i = -1.0 then
|
||
|
"-i"
|
||
|
else
|
||
|
(string_of_real_s i) ^ "i"
|
||
|
end
|
||
|
else
|
||
|
if i = 1.0 then
|
||
|
"+i"
|
||
|
else
|
||
|
"+" ^ (string_of_real_s i) ^ "i")
|
||
|
;;
|
||
|
|
||
|
let ichr i =
|
||
|
if i < 10 then
|
||
|
char_of_int (int_of_char '0' + i)
|
||
|
else
|
||
|
char_of_int (int_of_char 'a' + i - 10)
|
||
|
;;
|
||
|
|
||
|
let string_of_list l =
|
||
|
let n = List.length l in
|
||
|
let s = String.create n in
|
||
|
let rec loop i l =
|
||
|
if i < n then
|
||
|
begin
|
||
|
match l with
|
||
|
c::t -> s.[i] <- c; loop (i + 1) t
|
||
|
| _ -> assert false
|
||
|
end
|
||
|
else
|
||
|
()
|
||
|
in
|
||
|
loop 0 l; s
|
||
|
;;
|
||
|
|
||
|
let itostr base i =
|
||
|
if i = 0 then
|
||
|
"0"
|
||
|
else
|
||
|
let pf = if i < 0 then "-" else "" in
|
||
|
let rec loop i r =
|
||
|
if i = 0 then
|
||
|
r
|
||
|
else
|
||
|
loop (i / base) ((ichr (i mod base))::r)
|
||
|
in
|
||
|
pf ^ string_of_list (loop (abs i) [])
|
||
|
;;
|
||
|
|
||
|
let biqmi bi i =
|
||
|
let (q, r) = quomod_big_int bi (big_int_of_int i) in
|
||
|
(q, int_of_big_int r)
|
||
|
;;
|
||
|
|
||
|
let bitostr base bi =
|
||
|
let pf = if sign_big_int bi < 0 then "-" else "" in
|
||
|
let rec loop bi r =
|
||
|
if sign_big_int bi = 0 then
|
||
|
begin
|
||
|
match r with
|
||
|
[] -> [ '0' ]
|
||
|
| _ -> r
|
||
|
end
|
||
|
else
|
||
|
let (q, m) = biqmi bi base in
|
||
|
loop q ((ichr m)::r)
|
||
|
in
|
||
|
pf ^ string_of_list (loop (abs_big_int bi) [])
|
||
|
;;
|
||
|
|
||
|
let ntostr base =
|
||
|
function
|
||
|
Sint i -> itostr base i
|
||
|
| Sbigint bi -> bitostr base bi
|
||
|
| Srational r -> bitostr base (numerator_ratio r) ^ "/" ^
|
||
|
bitostr base (denominator_ratio r)
|
||
|
| _ -> raise (Error "number->string: invalid radix for inexact number")
|
||
|
;;
|
||
|
|
||
|
let rec snum_numtostr =
|
||
|
function
|
||
|
[| Sint i |] -> Sstring (string_of_int i)
|
||
|
| [| Sbigint bi |] -> Sstring (string_of_big_int bi)
|
||
|
| [| Srational r |] -> Sstring (string_of_ratio r)
|
||
|
| [| Sreal r |] -> Sstring (string_of_real r)
|
||
|
| [| Scomplex z |] -> Sstring (string_of_complex z)
|
||
|
| [| snum; Sint radix |] ->
|
||
|
if radix = 10 then
|
||
|
snum_numtostr [| snum |]
|
||
|
else if radix = 2 || radix = 8 || radix = 16 then
|
||
|
Sstring (ntostr radix snum)
|
||
|
else
|
||
|
raise (Error "number->string: invalid radix")
|
||
|
| _ -> raise (Error "number->string: bad args")
|
||
|
;;
|
||
|
|
||
|
let init e =
|
||
|
set_pfn e snum_strtonum "string->number";
|
||
|
set_pfn e snum_numtostr "number->string";
|
||
|
;;
|
||
|
|