Source file Scratch_text.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
open Scratch_blocks
exception Bad of string
let join pieces =
List.fold_left (fun acc p -> if acc = "" then p else if p = "?" then acc ^ p else acc ^ " " ^ p) "" pieces
let rec slot_text part a =
match (a, part) with
| Block b, _ -> reporter b
| Lit s, Num _ -> "(" ^ s ^ ")"
| Lit s, Menu _ -> "[" ^ s ^ " v]"
| Lit s, Bool -> "<" ^ s ^ ">"
| Lit s, _ when s <> "" && float_of_string_opt s <> None -> "(" ^ s ^ ")"
| Lit s, _ -> "[" ^ s ^ "]"
and reporter (b : block) =
match (b.op, b.args, (spec b.op).shape) with
| "data_variable", [ Lit name ], _ -> "(" ^ name ^ ")"
| _, _, Command_ring -> (
match List.map String.trim (stack 0 (List.concat b.mouths)) with [] -> "({ })" | body -> "({ " ^ String.concat "; " body ^ " })")
| _, _, Predicate -> "<" ^ line b ^ ">"
| _ -> "(" ^ line b ^ ")"
and lines (b : block) =
let args = ref b.args in
let next () = match !args with a :: rest -> args := rest; a | [] -> Lit "" in
List.map (fun parts -> join (List.map (function Word w -> w | part -> slot_text part (next ())) parts)) (spec b.op).lines
and line b = List.hd (lines b)
and stack indent blocks = List.concat_map (block indent) blocks
and block indent (b : block) =
let pad = String.make indent ' ' in
match lines b with
| _ when List.mem (spec b.op).shape [ Reporter; Predicate; Ring; Command_ring ] -> [ pad ^ reporter b ]
| first :: others when b.mouths <> [] ->
let rec mouths ms ls =
match (ms, ls) with
| m :: ms, l :: ls -> stack (indent + 2) m @ [ pad ^ l ] @ mouths ms ls
| [ m ], [] -> stack (indent + 2) m @ [ pad ^ "end" ]
| m :: ms, [] -> stack (indent + 2) m @ mouths ms []
| [], _ -> [ pad ^ "end" ]
in
(pad ^ first) :: mouths b.mouths others
| l :: _ -> [ pad ^ l ]
| [] -> []
let print scripts = String.concat "\n\n" (List.map (fun s -> String.concat "\n" (stack 0 s.blocks)) scripts) ^ "\n"
type tok = W of string | P of string | S of string | A of string
let rec closing s i =
let n = String.length s in
let rec go j depth =
if j >= n then raise (Bad ("no closing bracket in: " ^ s))
else
match (s.[i], s.[j]) with
| _, '[' when s.[i] <> '[' -> go (String.index_from s j ']' + 1) depth
| '[', ']' -> j
| '(', '(' -> go (j + 1) (depth + 1)
| '(', ')' -> if depth = 1 then j else go (j + 1) (depth - 1)
| '<', '(' -> go (closing s j + 1) depth
| '<', '<' when j + 1 < n && s.[j + 1] <> ' ' -> go (j + 1) (depth + 1)
| '<', '>' when s.[j - 1] <> ' ' -> if depth = 1 then j else go (j + 1) (depth - 1)
| _ -> go (j + 1) depth
in
go (i + 1) 1
let tokenize s =
let n = String.length s in
let rec go i acc =
if i >= n then List.rev acc
else
match s.[i] with
| ' ' -> go (i + 1) acc
| ('(' | '[') as c ->
let j = closing s i in
let inner = String.sub s (i + 1) (j - i - 1) in
go (j + 1) ((if c = '(' then P inner else S inner) :: acc)
| '<' when i + 1 < n && s.[i + 1] <> ' ' ->
let j = closing s i in
go (j + 1) (A (String.sub s (i + 1) (j - i - 1)) :: acc)
| _ ->
let rec word j = if j < n && s.[j] <> ' ' && s.[j] <> '(' && s.[j] <> '[' then word (j + 1) else j in
let j = word i in
go j (W (String.sub s i (j - i)) :: acc)
in
go 0 []
let is_number s = s = "" || float_of_string_opt s <> None
let semicolons s =
let n = String.length s in
let rec go i depth start acc =
if i >= n then List.rev (String.sub s start (n - start) :: acc)
else
match s.[i] with
| '(' | '[' | '{' -> go (i + 1) (depth + 1) start acc
| ')' | ']' | '}' -> go (i + 1) (depth - 1) start acc
| ';' when depth = 0 -> go (i + 1) depth (i + 1) (String.sub s start (i - start) :: acc)
| _ -> go (i + 1) depth start acc
in
List.filter (( <> ) "") (List.map String.trim (go 0 0 0 []))
let stack_shapes = [ Hat; Stack; Cap; C_block; C_cap ]
let rec fit parts toks =
match (parts, toks) with
| [], [] -> Some []
| Word w :: parts, W t :: toks when String.lowercase_ascii w = String.lowercase_ascii t -> fit extra parts toks
| (Num _ | Text _ | Menu _ | Lambda _) :: parts, ((P _ | S _ | A _) as t) :: toks -> Option.map (fun rest -> arg extra t :: rest) (fit extra parts toks)
| Bool :: parts, (A _ as t) :: toks -> Option.map (fun rest -> arg extra t :: rest) (fit extra parts toks)
| _ -> None
and arg = function
| S s ->
let l = String.length s in
Lit (if l >= 2 && String.sub s (l - 2) 2 = " v" then String.sub s 0 (l - 2) else s)
| P s -> if is_number (String.trim s) then Lit (String.trim s) else Block (inside extra s [ Reporter; Predicate; Ring ])
| A s -> if String.trim s = "" then Lit "" else Block (inside extra s [ Predicate ])
| W w -> Lit w
and inside s shapes =
let t = String.trim s in
match find extra (tokenize s) shapes with
| _ when List.mem Reporter shapes && List.mem t extra.names -> variable t
| Some b -> b
| None when List.mem Ring shapes && String.length t >= 2 && t.[0] = '{' && t.[String.length t - 1] = '}' ->
let body, _ = stack extra (semicolons (String.sub t 1 (String.length t - 2))) in
{ (make "snap_reifyscript") with mouths = [ body ] }
| None -> if List.mem Reporter shapes then variable t else raise (Bad ("I don't know the block <" ^ s ^ ">"))
and matches toks shapes =
List.filter_map
(fun (sp : spec) ->
if sp.op = "data_variable" || not (List.mem sp.shape shapes) then None
else Option.map (fun args -> { (make sp.op) with args }) (fit extra (List.hd sp.lines) toks))
(specs @ snap_specs @ extra.customs)
and find toks shapes = match matches extra toks shapes with b :: _ -> Some b | [] -> None
and stack lines =
match lines with
| [] -> ([], [])
| ("end" | "else") :: _ -> ([], lines)
| l :: rest ->
let rec first = function
| [ b ] -> mouths extra b rest
| b :: others -> ( try mouths extra b rest with Bad _ -> first others)
| [] -> raise (Bad ("I don't know the block: " ^ l))
in
let candidates =
match tokenize l with
| [ ((P _ | A _) as tok) ] -> ( match arg extra tok with Block b -> [ b ] | Lit _ -> [])
| toks -> matches extra toks stack_shapes
in
let b, rest = first candidates in
let tail, rest = stack extra rest in
(b :: tail, rest)
and mouths b rest =
if b.mouths = [] then (b, rest)
else
let n = List.length b.mouths in
let rec go k rest acc =
let body, rest = stack extra rest in
match rest with
| "else" :: rest when k < n - 1 -> go (k + 1) rest (body :: acc)
| "end" :: rest -> (List.rev (body :: acc), rest)
| [] -> (List.rev (body :: acc), [])
| l :: _ -> raise (Bad ("an " ^ l ^ " that does not belong here"))
in
let ms, rest = go 0 rest [] in
({ b with mouths = ms @ List.init (n - List.length ms) (fun _ -> []) }, rest)
let definitions lines =
let defined =
List.filter_map
(fun l ->
match tokenize l with
| [ W "define"; S kind; S template ] ->
let kind = if String.length kind > 2 && String.sub kind (String.length kind - 2) 2 = " v" then String.sub kind 0 (String.length kind - 2) else kind in
Some (spec (custom_op kind template), params template)
| _ -> None
| exception Bad _ -> None)
lines
in
{ customs = List.map fst defined; names = List.concat_map snd defined }
let parse text =
let strip l = match String.index_opt l '/' with Some i when i + 1 < String.length l && l.[i + 1] = '/' -> String.sub l 0 i | _ -> l in
let lines = List.map (fun l -> String.trim (strip l)) (String.split_on_char '\n' text) in
let close group groups = if group = [] then groups else List.rev group :: groups in
let group, groups = List.fold_left (fun (group, groups) l -> if l = "" then ([], close group groups) else (l :: group, groups)) ([], []) lines in
let groups = List.rev (close group groups) in
try
let = definitions lines in
Ok
(List.map
(fun g ->
match stack extra g with
| blocks, [] -> { x = 0.; y = 0.; blocks }
| _, l :: _ -> raise (Bad ("an " ^ l ^ " without its block")))
groups)
with Bad msg -> Error msg | Not_found -> Error "a bracket not closed"