-
Notifications
You must be signed in to change notification settings - Fork 55
Expand file tree
/
Copy pathpython.coffee
More file actions
186 lines (163 loc) · 6.58 KB
/
python.coffee
File metadata and controls
186 lines (163 loc) · 6.58 KB
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
_ = window?._ ? self?._ ? global?._ ? require 'lodash' # rely on lodash existing, since it busts CodeCombat to browserify it--TODO
parserHolder = {}
traversal = require '../traversal'
Language = require './language'
module.exports = class Python extends Language
name: 'Python'
id: 'python'
parserID: 'filbert'
thisValue: 'self'
thisValueAccess: 'self.'
heroValueAccess: 'hero.'
wrappedCodeIndentLen: 4
constructor: ->
super arguments...
@injectCode = require 'aether-lang-stdlibs/python-stdlib.ast.json'
@indent = Array(@wrappedCodeIndentLen + 1).join ' '
unless parserHolder.parser?.pythonRuntime?
if parserHolder.parser?
console.log 'Aether python parser ONLY missing pythonRuntime'
parserHolder.parser = self?.aetherFilbert ? require 'skulpty'
unless parserHolder.parser.pythonRuntime
console.error "Couldn't import Python runtime; our filbert import only gave us", parserHolder.parser
parserHolder.parserLoose ?= self?.aetherFilbertLoose ? require 'skulpty'
@runtimeGlobals =
__pythonRuntime: parserHolder.parser.pythonRuntime
hasChangedASTs: (a, b) ->
try
[aAST, bAST] = [null, null]
options = {locations: false, ranges: false}
aAST = parserHolder.parserLoose.parse_dammit a, options
bAST = parserHolder.parserLoose.parse_dammit b, options
unless aAST and bAST
return true
return not _.isEqual(aAST, bAST)
catch error
return true
# Replace 'loop:' with 'while True:'
replaceLoops: (rawCode) ->
# rawCode is pre-wrap
return [rawCode, []] if not rawCode.match(/^\s*loop/m)
convertedCode = ""
@replacedLoops = []
problems = []
rangeIndex = 0
lines = rawCode.split '\n'
for line, lineNumber in lines
if line.match(/^\s*loop\b/, "") and lineNumber < lines.length - 1
start = line.indexOf 'loop'
end = start + 4
end++ while (end < line.length and line[end].match(/\s/))
if line[end] != ':'
problems.push
type: 'transpile'
message: "You are missing a ':' after 'loop'. Try `loop:`"
range: [
row: lineNumber
column: start
,
row: lineNumber
column: end
]
a = line.split("")
a[start..end] = 'while True:'.split ""
line = a.join("")
@replacedLoops.push rangeIndex + start
convertedCode += line
convertedCode += '\n' unless lineNumber is lines.length - 1
rangeIndex += line.length + 1 # + 1 for newline
[convertedCode, @replacedLoops, problems]
# Return an array of UserCodeProblems detected during linting.
lint: (rawCode, aether) ->
problems = []
try
ast = parserHolder.parser.parse rawCode, locations: true, ranges: true, allowReturnOutsideFunction: true
# Check for empty loop
traversal.walkASTCorrect ast, (node) =>
return unless node.type is "WhileStatement"
return unless node.body.body.length is 0
# Craft an warning for empty loop
problems.push
type: 'transpile'
reporter: 'aether'
level: 'warning'
message: "Empty loop. Put 4 spaces in front of statements inside loops. Hint: Press \"Tab\" a the start of a new line."
range: [
ofs: node.range[0]
row: node.loc.start.line - 1
col: node.loc.start.column
,
ofs: node.range[1]
row: node.loc.end.line - 1
col: node.loc.end.column
]
# Check for empty if
if problems.length is 0
traversal.walkASTCorrect ast, (node) =>
return unless node.type is "IfStatement"
return unless node.consequent.body.length is 0
# Craft an warning for empty loop
problems.push
type: 'transpile'
reporter: 'aether'
level: 'warning'
# TODO: Try 'belong to' instead of 'inside' if players still have problems
message: "Empty if statement. Put 4 spaces in front of statements inside the if statement. Hint: Press \"Tab\" a the start of a new line."
range: [
ofs: node.range[0]
row: node.loc.start.line - 1
col: node.loc.start.column
,
ofs: node.range[1]
row: node.loc.end.line - 1
col: node.loc.end.column
]
catch error
problems
usesFunctionWrapping: () -> false
removeWrappedIndent: (range) ->
# Assumes range not in @wrappedCodePrefix
range = _.cloneDeep range
range
# Using a third-party parser, produce an AST in the standardized Mozilla format.
parse: (code, aether) ->
ast = parserHolder.parser.parse code, {locations: false, ranges: true, allowReturnOutsideFunction: true}
selfToThis ast
ast
parseDammit: (code, aether) ->
try
ast = parserHolder.parserLoose.parse_dammit code, {locations: false, ranges: true}
selfToThis ast
catch error
ast = {type: "Program", body:[{"type": "EmptyStatement"}]}
ast
convertToNativeType: (obj) ->
parserHolder.parser.pythonRuntime.utils.convertToList(obj) if not obj?._isPython and _.isArray obj
parserHolder.parser.pythonRuntime.utils.convertToDict(obj) if not obj?._isPython and _.isObject obj
obj
cloneObj: (obj, cloneFn=(o) -> o) ->
if _.isArray obj
result = new parserHolder.parser.pythonRuntime.objects.list()
result.append(cloneFn v) for v in obj
else if _.isObject obj
result = new parserHolder.parser.pythonRuntime.objects.dict()
result[k] = cloneFn v for k, v of obj
else
result = cloneFn obj
result
selfToThis = (ast) ->
ast.body.unshift {"type": "VariableDeclaration","declarations": [{ "type": "VariableDeclarator", "id": {"type": "Identifier", "name": "self" },"init": {"type": "ThisExpression"} }],"kind": "var", "userCode": false} # var self = this;
ast
setupInterpreter: (esper) ->
realm = esper.realm
realm.options.linkValueCallReturnValueWrapper = (value) ->
ArrayPrototype = realm.ArrayPrototype
return value unless value.jsTypeName is 'object'
if value.clazz is 'Array'
defineProperties = realm.Object.getImmediate('defineProperties');
listPropertyDescriptor = realm.globalScope.get('__pythonRuntime').getImmediate('utils').getImmediate('listPropertyDescriptor');
gen = defineProperties.call realm.Object, [value, listPropertyDescriptor], realm.globalScope
it = gen.next()
while not it.done
it = gen.next()
return value