package moss

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file moss.ml

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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
(* File: moss.ml

   Copyright (C) 2017-

     Christophe Troestler <Christophe.Troestler@umons.ac.be>
     WWW: http://math.umons.ac.be/an/software/

   Permission to use, copy, modify, and/or distribute this software
   for any purpose with or without fee is hereby granted, provided
   that the above copyright notice and this permission notice appear
   in all copies.

   THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
   WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
   WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
   AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
   CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
   OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
   NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
   CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.  *)

open Printf
(* open Lwt *)

let min (x: int) (y: int) = if x <= y then x else y

type lang =
  | C | CC | Java | Ml | Pascal | Ada | Lisp | Scheme | Haskell
  | Fortran | Ascii | Vhdl | Perl | Matlab | Python | Mips | Prolog
  | Spice | VB | Csharp | Modula2 | A8086 | Javascript | Plsql

let string_of_lang = function
  | C -> "c"
  | CC -> "cc"
  | Java -> "java"
  | Ml -> "ml"
  | Pascal -> "pascal"
  | Ada -> "ada"
  | Lisp -> "lisp"
  | Scheme -> "scheme"
  | Haskell -> "haskell"
  | Fortran -> "fortran"
  | Ascii -> "ascii"
  | Vhdl -> "vhdl"
  | Perl -> "perl"
  | Matlab -> "matlab"
  | Python -> "python"
  | Mips -> "mips"
  | Prolog -> "prolog"
  | Spice -> "spice"
  | VB -> "vb"
  | Csharp -> "csharp"
  | Modula2 -> "modula2"
  | A8086 -> "a8086"
  | Javascript -> "javascript"
  | Plsql -> "plsql"

let server = "moss.stanford.edu"
let server_ip = Unix.inet_addr_of_string "171.64.78.49"
let port = 7690
let server_addr =
  try
    let h = Unix.gethostbyname server in
    Unix.ADDR_INET(h.Unix.h_addr_list.(0), port)
  with _ ->
    Unix.ADDR_INET(server_ip, port) (* fallback *)

(* userid seem to be of the type "[0-9]+".  However, we do not have
   guarantees about them, they could start with 0 or exceed the
   capacity of native integers (Perl can store numbers as "decimal
   strings"). *)
let check_userid ~err id =
  for i = 0 to String.length id - 1 do
    if id.[i] < '0' || '9' < id.[i] then invalid_arg err
  done

let default_userid =
  ref(try let id = Sys.getenv "MOSS_USERID" in
          check_userid id ~err:"The shell variable MOSS_USERID should only \
                                be made of digits 0-9.";
          id
      with Not_found -> "0")

let set_userid id =
  check_userid id ~err:"Moss.set_userid: the userid must only be \
                        made of digits 0-9";
  default_userid := id

let get_userid () = !default_userid


module File = struct
  class type in_obj_channel =
    object
      method input : Bytes.t -> int -> int -> int
      method close_in : unit -> unit
    end

  type data =
    | String of string (* content *)
    | Buffer of Buffer.t
    | File of string   (* full-path *)
    | Channel of (unit -> in_channel) (* (size, create-channel) *)
    | Obj of (unit -> in_obj_channel) (* (size, create-channel) *)

  type t = {
      name: string;
      mutable data: data;
      (* Some operations may alter the representation of the data. *)
      mutable size: int;  (* < 0 if it has to be computed *)
    }

  let name t = t.name

  let of_string ~name content =
    { name;  data = String content;  size = String.length content }

  let of_path ?name path =
    (* FIXME: check [name] and [path]? *)
    let name = match name with None -> path
                             | Some n -> n in
    (* The user can change directory but we to not want that the data
       becomes inaccessible because of that.  Keep a full path. *)
    let path = if Filename.is_relative path then
                 Filename.concat (Sys.getcwd()) path
               else path in
    { name;  data = File path;  size = Unix.((stat path).st_size) }

  let of_in_channel ?size ~name create =
    let size = match size with
      | Some sz ->
         if sz < 0 then
           invalid_arg "Moss.File.of_in_channel: ~size >= 0 required";
         sz
      | None -> -1 in
    { name;  data = Channel create;  size }

  let of_in_obj ?size ~name create_in_ch =
    let size = match size with
      | Some sz ->
         if sz < 0 then
           invalid_arg "Moss.File.of_in_obj: ~size >= 0 required";
         sz
      | None -> -1 in
    { name;  data = Obj create_in_ch;  size }

  (* Read until End_of_file into buffer [buf]. *)
  let read_in_buffer buf fh =
    set_binary_mode_in fh true;
    let b = Bytes.create 4096 in
    let len = ref 0 in
    while len := input fh b 0 4096;  !len > 0 do
      Buffer.add_subbytes buf b 0 !len;
    done

  let read_obj_in_buffer buf obj =
    let b = Bytes.create 4096 in
    try
      while true do
        let len = obj#input b 0 4096 in
        if len = 0 then
          (* Small pause to avoid a busy loop. *)
          ignore(Unix.select [] [] [] 0.1)
        else
          Buffer.add_subbytes buf b 0 len
      done
    with End_of_file -> ()

  (* May alter the data because, for some targets, it is necessary to
     read the data to determine the size. *)
  let size f =
    if f.size >= 0 then f.size
    else match f.data with
         | String _ | Buffer _ | File _ -> assert false
         | Channel create ->
            let fh = create () in
            let buf = Buffer.create 4096 in
            read_in_buffer buf fh;
            close_in fh;
            f.data <- Buffer buf;
            f.size <- Buffer.length buf;
            f.size
         | Obj create ->
            let obj = create() in
            let buf = Buffer.create 4096 in
            read_obj_in_buffer buf obj;
            obj#close_in();
            f.data <- Buffer buf;
            f.size <- Buffer.length buf;
            f.size

  exception Not_enough_data_on_channel
  (* Exception raised when a "file" does not contain enough data.  We
     have to abort the upload in this case because the file size was
     already sent. *)

  let copy_chunk_to_channel out_fh ~size in_fh =
    set_binary_mode_in in_fh true;
    let b = Bytes.create 4096 in
    let size = ref size in
    let len = ref 0 in
    while !size > 0 && (len := input in_fh b 0 (min 4096 !size);  !len > 0) do
      output out_fh b 0 !len;
      size := !size - !len;
    done;
    close_in in_fh;
    if !size > 0 then raise Not_enough_data_on_channel

  let copy_obj_to_channel out_fh ~size (obj: in_obj_channel) =
    let b = Bytes.create 4096 in
    try
      let size = ref size in
      while !size > 0 do
        let len = obj#input b 0 (min 4096 !size) in
        if len = 0 then
          (* Small pause to avoid a busy loop. *)
          ignore(Unix.select [] [] [] 0.1)
        else (
          output out_fh b 0 len;
          size := !size - len;
        )
      done;
      obj#close_in()
    with End_of_file ->
      obj#close_in();
      raise Not_enough_data_on_channel

  (* [id] = 0 ⇒ base file *)
  let upload out_fh file ~id ~lang =
    let size = size file in
    fprintf out_fh "file %d %s %d %s\n" id lang size file.name;
    try
      match file.data with
      | String c -> output_string out_fh c
      | Buffer buf -> Buffer.output_buffer out_fh buf
      | File path ->
         let fh = open_in path in
         copy_chunk_to_channel out_fh ~size fh
      | Channel create -> let fh = create () in
                          copy_chunk_to_channel out_fh ~size fh
      | Obj create -> let obj = create () in
                      copy_obj_to_channel out_fh ~size obj
    with Not_enough_data_on_channel ->
      failwith(sprintf "File %S has length less than the requested \
                        %d bytes" file.name size)
end


let submit ?(userid= !default_userid) ?(experimental=false) ?comment
      ?(by_dir=false) ?(max_rep=10) ?(n=250) lang ?(base=[]) files =
  check_userid userid ~err:"Moss.submit: userid must only be made of \
                            digits 0-9";
  let by_dir = if by_dir then '1' else '0' in
  let experimental = if experimental then '1' else '0' in
  if max_rep < 2 then invalid_arg "Moss.submit: ~max_rep must be >= 2";
  if n < 2 then invalid_arg "Moss.submit: ~n must be >= 2";
  let lang = string_of_lang lang in
  let comment = match comment with
    | Some c ->
       for i = 0 to String.length c - 1 do
         if c.[i] < ' ' || c.[i] > '}' then
           failwith "Moss.submit: comment can only contain printable \
                     ASCII characters"
       done;
       c
    | None -> "" in
  let in_fh, out_fh = Unix.open_connection server_addr in
  let close_sock () =
    output_string out_fh "end\n";
    flush out_fh;
    Unix.shutdown_connection in_fh in
  try
    (* FIXME: Do we want to introduce timeouts? *)
    fprintf out_fh "moss %s\ndirectory %c\nX %c\nmaxmatches %d\nshow %d\n\
                    language %s\n%!"
      userid by_dir experimental max_rep n lang;
    let r = input_line in_fh in
    if r = "no" then
      failwith ("Moos.connect: unrecognized language " ^ lang);
    (* Upload any base file. *)
    List.iter (fun fn -> File.upload out_fh fn ~id:0 ~lang) base;
    (* Upload other files. *)
    List.iteri (fun i fn -> File.upload out_fh fn ~id:(i+1) ~lang) files;
    (* Check not all sizes are = 0. *)
    if List.for_all (fun fn -> File.size fn = 0) files then
      failwith "Moss.submit: At least one file must have non-zero length";
    fprintf out_fh "query 0 %s\n%!" comment;
    let r, _, _ = Unix.select [Unix.descr_of_in_channel in_fh] [] [] 10. in
    match r with
    | [] -> failwith "Moss.submit: timeout to receive the URL"
    | _ :: _ ->
       match input_line in_fh with
       | url ->
          close_sock();
          Uri.of_string url
       | exception End_of_file ->
          failwith "Moss.submit: no URL returned by the server"
  with Failure _ as e ->
    close_sock();
    raise e

;;
(* Local Variables: *)
(* compile-command: "make -k -C.. build" *)
(* End: *)