-
Notifications
You must be signed in to change notification settings - Fork 25
/
eval.js
332 lines (318 loc) · 14.9 KB
/
eval.js
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
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
var prefix_to_evaled_src = "try{" //referenced in eval code AND in error handler way below
function char_position(src, line_number, col_number){
// line_number is 1 based. as are chrome error messages
// col_number is 1 based. as are chrome error messages
// result is 0 based.
// beware codemirror has line and char positions being 0 based, and its index 0 based as well
// assume src linefeeds are just 1 char ie \n
// and make that newline char be the last char on the line, not the first char of the next line.
var cur_line = 1
var cur_col = 0
var prev_char = null
for (var i = 0; i < src.length; i++){
var char = src.charAt(i)
if (prev_char == '\n'){
cur_line = cur_line + 1
cur_col = 1
}
else {
cur_col += 1
}
if ((cur_line == line_number) && (cur_col == col_number)){
return i;
}
prev_char = char
}
return false
}
function fix_code_to_be_evaled(src){
let slash_slash_pos = src.lastIndexOf("//")
let newline_pos = src.lastIndexOf("\n")
if (slash_slash_pos > newline_pos) { //src is ending with a slash-slash comment but no newline on end.
return src + "\n" //without this I get an error
}
else { return src }
}
//a string or null indicating eval button hasn't been clicked since dde launch.
//used in make_dde_status_report
var latest_eval_button_click_source = null
//part 1 of 3.
//Only called by eval_button_action
//when this is called, there is no selection, so either we're evaling the whole editor buffer
//or the whole cmd line.
//beware, the code *might* be HTML or python at this point
function eval_js_part1(step=false){
//tricky: when button is clicked, Editor.get_any_selection() doesn't work,
//I guess because the button itself is now in focus,
//so we grab the selection on mousedown of the the Eval button.
//then use that here if its not "", otherwise, Editor.get_javascript("auto"), getting the whol editor buffer
let src
let src_comes_from_editor = false
if(previous_active_element &&
previous_active_element.parentNode &&
previous_active_element.parentNode.parentNode &&
previous_active_element.parentNode.parentNode.CodeMirror){
src = Editor.get_javascript("auto") //if sel in editor, get it, else get whole editor
src_comes_from_editor = true
}
//let sel_obj = window.getSelection()
else if (selected_text_when_eval_button_clicked.length > 0) {
src = selected_text_when_eval_button_clicked
}
else if (previous_active_element &&
previous_active_element.tagName == "TEXTAREA"){
let start = previous_active_element.selectionStart
let end = previous_active_element.selectionEnd
if (start != end) { src = previous_active_element.value.substring(start, end) }
else { src = previous_active_element.value }
}
else if (previous_active_element &&
(previous_active_element.tagName == "INPUT") &&
(previous_active_element.type == "text")){
let start = previous_active_element.selectionStart
let end = previous_active_element.selectionEnd
if (start != end) { src = previous_active_element.value.substring(start, end) }
else { src = previous_active_element.value }
src = previous_active_element.value
}
else {
src = Editor.get_javascript("auto")
}
//we do NOT want to pass to eval part 2 a trimmed string as getting its char
//offsets into the editor buffer correct is important.
latest_eval_button_click_source = src
if (src.trim() == ""){
open_doc(learning_js_doc_id)
if(src.length > 0) {
warning("There is a selection in the editor but it has whitespace only<br/>" +
"so there is no code to execute.<br/>" +
"If you intended to eval the whole editor buffer,<br/>" +
"click to eliminate the selection,<br/>" +
"then click the Eval button again.")
}
else {
warning("There is no code to execute.<br/>See <span style='color:black;'>Learning JavaScript</span> " +
"in the Documentation pane for help.")
}
}
else{
if (Editor.view == "DefEng") {
try {src = DE.de_to_js(src) }
catch(e) { dde_error("Error parsing DefEng: " + e.message) }
//out("<code>" + src + "</code>") //don't need this as JS is printed in Output pane after "Eval result of"
} //must add "d ebugger" after converting DefEng to JS.
else if (window.HCA && (Editor.view === "HCA")){
HCA.eval_button_action(step)
return
}
if(html_db.string_looks_like_html(src)){
render_html(src)
}
else if(Editor.current_file_path.endsWith(".py") &&
src_comes_from_editor
){
Py.eval_part2(src)
}
else {
let src_for_cmd_menu_maybe = src.trim()
if(!src_for_cmd_menu_maybe.includes("\n")){ //its a one-liner
js_cmds_array.push(src_for_cmd_menu_maybe) //just leave the "index" into the array where-ever it is.
}
eval_js_part2((step? "debugger; ": "") + src) //LEAVE THIS IN RELEASED CODE
}
}
}
function render_html(str){
let title_suffix = str.substring(0, 50) //14
if(str.length > title_suffix.length) { title_suffix += "..." }
title_suffix = replace_substrings(title_suffix, "<", "<")
title_suffix = replace_substrings(title_suffix, '"', """)
let str_for_title = replace_substrings(str, '"', """)
//let title = 'Rendering HTML: <span title="' + str_for_title + '">' + title_suffix + '</span>'
//show_window({title: title, content: str})
out_eval_result(str, "#000000", str_for_title, "The result of rendering HTML")
}
//part 2 of 3 is in eval.js, window.addEventListener('message' under the message name of "eval"
function eval_js_part2(command, call_eval_part3_if_no_error=true){ //2nd arg passed in as false for eval_and_play
command = fix_code_to_be_evaled(command)
let suffix_to_evaled_src
let prefix_to_evaled_src
if (command.startsWith("{")) {
prefix_to_evaled_src = "try{(";
suffix_to_evaled_src = ")}"
} //parens fixes broken js eval of src like "{a:2, b:3}"
else {
prefix_to_evaled_src = "try{" //no parens, normal case
suffix_to_evaled_src = "}"
}
//full_dexter_url = event.data.full_dexter_url
var result = {command: command}
try {//note doing try_command = "var val927; try{val927 = " + command ...fails because evaling
//"var f1 = function f2(){...}" will bind f1 to the fn but NOT f2!!! what a piece of shit js is.
//AND wrapping parens around src of "{a:2, b:3}" works but wrapping parens around a "function f1()..." doesn't get f1 bound to the fn.
var try_command = prefix_to_evaled_src + command + suffix_to_evaled_src + " catch(err){error_message_start_and_end_pos(err)}"
//if try succeeds, it returns its last val, else catch hits and it returns its last val
//if I don't do this trick with val927, I get an error when evaling "{a:2, b:3}
//I can't figure out whether try is supposed to return the val of its last exp or not from the js spec.
let start_time = Date.now()
var value = window.eval(try_command) //window.eval evals in "global scope" meaning that, unlike plain eval,
result.value = value //used by Job's menu item "Start job"
//if I click on EVAL button with window.eval for defining a fn,
//then a 2nd click on EVAL for calling it, it will work.
//also works with var foo = 2, and foo in separate eval clicks.
//also I think this global eval will not see var bindings for the
//lex vars in this eventListener, ie name, result, etc. which is good.
result.duration = Date.now() - start_time
if (value === null){ //calling null.error_type will error so do this first.
result.value_string = stringify_value(value)
}
else if ((typeof(value) == "object") && value.error_type){
result = value
result.command = command
result.starting_index = char_position(command, result.line_no, result.char_no)
var command_starting_with_starting_index = command.substring(result.starting_index)
result.ending_index = command_starting_with_starting_index.match(/\W|$/).index + result.starting_index
}
else{
result.value_string = stringify_value(value)
//result.command = command
}
}
catch(err) { //probably a syntax error. Can't get starting_index so won't be able to highlight offending code
result.error_type = err.name
result.full_error_message = err.stack
result.error_message = err.message
eval_js_part3(result)
return "Error: " + err.message
}
if (call_eval_part3_if_no_error) { eval_js_part3(result) }
return result
}
// in UI.
function eval_js_part3(result){
var string_to_print
var start_of_selection = 0
if (Editor.is_selection()){
start_of_selection = Editor.selection_start()
}
if (result.error_type){
string_to_print = result.error_type + ": " + result.error_message
if (result.starting_index != undefined) { //beware, starting_index might == 0 which is false to IF
var cm_pos = myCodeMirror.doc.posFromIndex(start_of_selection + result.starting_index)
string_to_print += "<br/> At line: " + (cm_pos.line + 1) + ", char: " + (cm_pos.ch + 1)
}
var stack_trace = result.full_error_message
var first_newline = stack_trace.indexOf("\n")
if (first_newline != -1) { stack_trace = stack_trace.substring(first_newline + 1) }
stack_trace = replace_substrings(stack_trace, "\n", "<br/>")
string_to_print = "<details><summary><span class='dde_error_css_class'>" + string_to_print +
"</span></summary>" + stack_trace + "</details>"
out_eval_result(string_to_print, undefined, result.command)
}
else if (result.value_string == '"dont_print"') {}
else {
if (inspect_is_primitive(result.value)) {
let str = result.value_string
if ((str.length > 2) &&
(str[0] == '"') &&
str.includes('\\"') &&
!str.includes("'")
) {
str = "'" + str.substring(1, str.length - 1) + "'"
str = replace_substrings(str , '\\\\"', '"')
}
string_to_print = str +
" <span style='padding-left:50px;font-size:10px;'>" + result.duration + " ms</span>" //beware, format_text_for_code depends on this exact string
out_eval_result(string_to_print, undefined, result.command)
}
else { inspect(result.value, result.command) }
}
//highlight erroring source code if possible. If result.starting_index == undefined, that means no error.
if (result.starting_index && (result.starting_index != 0)){ //we've got an error
//beware starting_index might be 0 which IF treats as false
if (result.ending_index == undefined){
result.ending_index = result.starting_index + 1
}
Editor.select_javascript(start_of_selection + result.starting_index, start_of_selection + result.ending_index)
}
//else if(Editor.get_cmd_selection.length > 0) { cmd_input_id.focus() }
}
//doesn't really need to return result as this side_effects result
//src evaled which might not be the whole editor buffer if there's a selection
function error_message_start_and_end_pos(err){
var error_result = {}
var full_mess = err.stack
error_result.full_error_message = full_mess
error_result.error_type = err.name
error_result.error_message = err.message
if (error_result.error_type == "SyntaxError"){ //Syntax errors don't contain line number info.
return error_result
}
else {
try {
var comma_split_part = full_mess.split(",")[1]
var colon_splits = comma_split_part.split(":")
var line_no = parseInt(colon_splits[1])
var char_part = colon_splits[2]
var paren_splits = char_part.split(")")
var char_no = parseInt(paren_splits[0])
if (line_no == 1){ //because of the "try{" that I wrap command in.
char_no = char_no - prefix_to_evaled_src.length //7 due to the prefix evaled, ie "try{("
}
error_result.line_no = line_no
error_result.char_no = char_no
return error_result
}
catch(e){return error_result} //because might be some weird format of err.stack
//that I can't parse, so just bail.
}
}
//action for the Eval&Start button
function eval_and_start(){
let sel_text = Editor.get_any_selection()
if (sel_text.length == 0) {
sel_text = Editor.get_javascript()
if (sel_text.length == 0) {
warning("There is no selection to eval.")
return
}
}
sel_text = sel_text.trim()
if(starts_with_one_of(sel_text, ["function ", "function(", "function*"])) {
sel_text = "(" + sel_text + ")"
} //necessary because
//without the parens, evaling an anonymous fn, named fn or gen def by itself errors (or returns undefined),
//due to JS design bug.
//BUT an anonymous fn as part of an array (as in the do_list) WILL return the fn def.
//wrapping parens causes eval to return the fn def so we can use it as a dp+list item
//and thus run it in a job.
let result = eval_js_part2(sel_text, false) //don't call eval part 3 if no error
if ((typeof(result) == "string") && result.startsWith("Error:")) {} //handled by eval_js_part3
else {
let val = result.value
let start_result
if(Instruction.is_start_object(val)) {
try { start_result = val.start() }
catch(err) {
warning("The result of evaling: " + sel_text +
"<br/> is: " + val +
"<br/> but calling its <code>start</code> method errored with:<br/>" +
err.message)
return
}
inspect(start_result, sel_text)
}
else if (Instruction.is_do_list_item(val)){
inspect(val, sel_text)
Robot.dexter0.run_instruction_fn(val)
}
else {
warning("The result of evaling: " + sel_text +
"<br/> is: " + val +
"<br/> but that isn't a do_list item and doesn't have a start method.")
}
}
}
var {out_eval_result} = require("./core/out.js")
var {replace_substrings, starts_with_one_of, stringify_value} = require("./core/utils.js")
var {Robot, Brain, Dexter, Human, Serial} = require('./core/robot.js')