-
Notifications
You must be signed in to change notification settings - Fork 96
/
Copy pathcodeWriter.js
245 lines (229 loc) · 9.72 KB
/
codeWriter.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
/**
Copyright 2014 Gordon Williams (gw@pur3.co.uk)
This Source Code is subject to the terms of the Mozilla Public
License, v2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
------------------------------------------------------------------
The plugin that actually writes code out to Espruino
------------------------------------------------------------------
**/
"use strict";
(function(){
function init() {
Espruino.Core.Config.add("RESET_BEFORE_SEND", {
section : "Communications",
name : "Reset before Send",
description : "Reset Espruino before sending code from the editor pane?",
type : "boolean",
defaultValue : true
});
Espruino.Core.Config.add("STORE_LINE_NUMBERS", {
section : "Communications",
name : "Store line numbers",
description : "Should Espruino store line numbers for each function? This uses one extra variable per function, but allows you to get source code debugging in the Web IDE",
type : "boolean",
defaultValue : true
});
}
function writeToEspruino(code, callback) {
/* hack around non-K&R code formatting that would have
broken Espruino CLI's bracket counting */
code = reformatCode(code);
if (code === undefined) return; // it should already have errored
// We want to make sure we've got a prompt before sending. If not,
// this will issue a Ctrl+C
Espruino.Core.Utils.getEspruinoPrompt(function() {
// Make sure code ends in 2 newlines
while (code[code.length-2]!="\n" || code[code.length-1]!="\n")
code += "\n";
// By sending an empty print, we remove the Espruino '>' prompt
// which then allows us to see if upload has finished
code = "\x10print()\n"+code;
// If we're supposed to reset Espruino before sending...
if (Espruino.Config.RESET_BEFORE_SEND) {
// reset Espruino
code = "\x10reset();\n"+code;
}
//console.log("Sending... "+data);
Espruino.Core.Serial.write(code, true, function() {
// give 5 seconds for sending with save and 2 seconds without save
var count = Espruino.Config.SAVE_ON_SEND ? 50 : 20;
setTimeout(function cb() {
if (Espruino.Core.Terminal!==undefined &&
!Espruino.Core.Terminal.getTerminalLine().startsWith(">")) {
count--;
if (count>0) {
setTimeout(cb, 100);
} else {
Espruino.Core.Notifications.error("Prompt not detected - upload failed. Trying to recover...");
Espruino.Core.Serial.write("\x03\x03echo(1)\n", false, callback);
}
} else {
if (callback) callback();
}
}, 100);
});
});
}
/**
* Parse and fix issues like `if (false)\n foo` in the root scope
* @param {string} code
* @returns {string | undefined}
*/
function reformatCode(code) {
var APPLY_LINE_NUMBERS = false;
var lineNumberOffset = 0;
var ENV = Espruino.Core.Env.getData();
if (ENV && ENV.VERSION_MAJOR && ENV.VERSION_MINOR) {
if (ENV.VERSION_MAJOR>1 ||
ENV.VERSION_MINOR>=81.086) {
if (Espruino.Config.STORE_LINE_NUMBERS)
APPLY_LINE_NUMBERS = true;
}
}
/* There's only any point writing line numbers when we're saving to RAM. Otherwise we just
end up applying line numbers to require("Storage").write lines. see https://github.com/espruino/EspruinoTools/pull/179 */
if (Espruino.Config.SAVE_ON_SEND != 0) {
APPLY_LINE_NUMBERS = false;
}
// Turn cr/lf into just lf (eg. windows -> unix)
code = code.replace(/\r\n/g,"\n");
// First off, try and fix funky characters
for (let i=0;i<code.length;i++) {
var ch = code.charCodeAt(i);
if ((ch<32 || ch>255) && ch!=9/*Tab*/ && ch!=10/*LF*/ && ch!=13/*CR*/) {
console.warn("codewriter> Unexpected character code "+ch+" at position "+i+". Replacing with ?");
code = code.substr(0,i)+"?"+code.substr(i+1);
}
}
/* Search for lines added to the start of the code by the module handler.
Ideally there would be a better way of doing this so line numbers stayed correct,
but this hack works for now. Fixes EspruinoWebIDE#140 */
if (APPLY_LINE_NUMBERS) {
var l = code.split("\n");
let i = 0;
while (l[i] && (l[i].substr(0,8)=="Modules." ||
l[i].substr(0,8)=="setTime(")) i++;
lineNumberOffset = -i;
}
var resultCode = "\x10"; // 0x10 = echo off for line
/** we're looking for:
* `a = \n b`
* `for (.....) \n X`
* `if (.....) \n X`
* `if (.....) { } \n else foo`
* `while (.....) \n X`
* `do \n X`
* `function (.....) \n X`
* `function N(.....) \n X`
* `var a \n , b` `var a = 0 \n, b`
* `var a, \n b` `var a = 0, \n b`
* `a \n . b`
* `foo() \n . b`
* `try { } \n catch \n () \n {}`
*
* These are divided into two groups - where there are brackets
* after the keyword (statementBeforeBrackets) and where there aren't
* (statement)
*
* We fix them by replacing \n with what you get when you press
* Alt+Enter (Ctrl + LF). This tells Espruino that it's a newline
* but NOT to execute.
*/
var lex = Espruino.Core.Utils.getLexer(code);
var brackets = 0;
var curlyBrackets = 0;
var statementBeforeBrackets = false;
var statement = false;
var varDeclaration = false;
var lastIdx = 0;
var lastTok = {str:""};
var tok = lex.next();
while (tok!==undefined) {
var previousString = code.substring(lastIdx, tok.startIdx);
var tokenString = code.substring(tok.startIdx, tok.endIdx);
//console.log("codewriter> prev "+JSON.stringify(previousString)+" next "+tokenString);
/* Inserting Alt-Enter newline, which adds newline without trying
to execute */
if (brackets>0 || // we have brackets - sending the alt-enter special newline means Espruino doesn't have to do a search itself - faster.
statement || // statement was before brackets - expecting something else
statementBeforeBrackets || // we have an 'if'/etc
varDeclaration || // variable declaration then newline
tok.str=="," || // comma on newline - there was probably something before
tok.str=="." || // dot on newline - there was probably something before
tok.str=="+" || tok.str=="-" || // +/- on newline - there was probably something before
tok.str=="=" || // equals on newline - there was probably something before
tok.str=="else" || // else on newline
lastTok.str=="else" || // else befgore newline
tok.str=="catch" || // catch on newline - part of try..catch
lastTok.str=="catch"
) {
//console.log("codewriter> Possible"+JSON.stringify(previousString));
previousString = previousString.replace(/\n/g, "\x1B\x0A");
}
var previousBrackets = brackets;
if (tok.str=="(" || tok.str=="{" || tok.str=="[") brackets++;
if (tok.str=="{") curlyBrackets++;
if (tok.str==")" || tok.str=="}" || tok.str=="]") brackets--;
if (tok.str=="}") curlyBrackets--;
if (brackets==0) {
if (tok.str=="for" || tok.str=="if" || tok.str=="while" || tok.str=="function" || tok.str=="throw") {
statementBeforeBrackets = true;
varDeclaration = false;
} else if (tok.str=="var") {
varDeclaration = true;
} else if (tok.type=="ID" && lastTok.str=="function") {
statementBeforeBrackets = true;
} else if (tok.str=="try" || tok.str=="catch") {
statementBeforeBrackets = true;
} else if (tok.str==")" && statementBeforeBrackets) {
statementBeforeBrackets = false;
statement = true;
} else if (["=","^","&&","||","+","+=","-","-=","*","*=","/","/=","%","%=","&","&=","|","|="].indexOf(tok.str)>=0) {
statement = true;
} else {
if (tok.str==";") varDeclaration = false;
statement = false;
statementBeforeBrackets = false;
}
}
/* If we're at root scope and had whitespace/comments between code,
remove it all and replace it with a single newline and a
0x10 (echo off for line) character. However DON'T do this if we had
an alt-enter in the line, as it was there to stop us executing
prematurely */
if (previousBrackets==0 &&
previousString.indexOf("\n")>=0 &&
previousString.indexOf("\x1B\x0A")<0) {
previousString = "\n\x10";
// Apply line numbers to each new line sent, to aid debugger
if (APPLY_LINE_NUMBERS && tok.lineNumber && (tok.lineNumber+lineNumberOffset)>0) {
// Esc [ 1234 d
// This is the 'set line number' command that we're abusing :)
previousString += "\x1B\x5B"+(tok.lineNumber+lineNumberOffset)+"d";
}
}
// add our stuff back together
resultCode += previousString+tokenString;
// next
lastIdx = tok.endIdx;
lastTok = tok;
tok = lex.next();
}
//console.log(resultCode);
if (brackets>0) {
Espruino.Core.Notifications.error("You have more open brackets than close brackets. Please see the hints in the Editor window.");
return undefined;
}
if (brackets<0) {
Espruino.Core.Notifications.error("You have more close brackets than open brackets. Please see the hints in the Editor window.");
return undefined;
}
return resultCode;
}
Espruino.Core.CodeWriter = {
init : init,
writeToEspruino : writeToEspruino, // call this to send to Espruino
reformatCode : reformatCode // Parse and fix issues like `if (false)\n foo` in the root scope
};
}());