/
CleanCodeGenerator.scala
288 lines (259 loc) · 11.6 KB
/
CleanCodeGenerator.scala
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
package ch.epfl.insynth.reconstruction.codegen
import ch.epfl.insynth.reconstruction.intermediate._
import ch.epfl.insynth.trees
import ch.epfl.scala.{ trees => Scala }
import ch.epfl.insynth.print._
import ch.epfl.insynth.reconstruction.combinator.{ NormalDeclaration, AbsDeclaration }
import scala.text.Document
import scala.text.Document.empty
import scala.text.DocNil
/** companion object for more convenient application */
object CleanCodeGenerator {
def apply(tree: Node) = {
(new CleanCodeGenerator).apply(tree)
}
}
/**
* this class support scala syntax without unnecessary parentheses and dots
*/
class CleanCodeGenerator extends CodeGenerator {
// import methods for easier document manipulation
import FormatHelpers._
import Document._
import TransformContext._
import DocumentHelper._
/**
* main method (recursive) for transforming a intermediate (sub)tree
* @param tree root node of the (sub)tree to transform
* @return list of documents containing all combinations of available expression for
* the given (sub)tree
*/
override def transform(tree: Node, ctx: TransformContext = Expr): List[Document] = {
// a local variable to set if parentheses are needed
var parenthesesRequired = true
// do parentheses for parameters if needed
def doParenApp(appId: Document, params: Document) =
params match {
case _ if parenthesesRequired => (appId :: paren(params))
case DocNil if !parenthesesRequired => appId
case _ => (appId :/: params)
}
def doParenRecApp(receiverDoc: Document, appId: Document, params: Document) = {
receiverDoc match {
case DocNil =>
// if receiver is empty then we always need this form
doParenApp(appId, params)
case _: Document =>
if (parenthesesRequired)
(receiverDoc :: "." :: appId :: paren(params))
else
(receiverDoc :/: appId :/?: params)
}
}
def doParen(d: Document) = if (parenthesesRequired) paren(d) else d
tree match {
// variable (declared previously as arguments)
// NOTE case when it is an argument (type is not needed)
//case Variable(tpe, name) if ctx==Arg => List ( group(name :: ": " :: transform(tpe) ) )
case Variable(tpe, name) => List(name)
// identifier from the scope
case Identifier(tpe, dec) =>
List(dec.getSimpleName)
// apply parameters in the tail of params according to the head of params
case Application(tpe, params) => {
// so far as we constructed, there should be only one application definition term
// (which tells us, whether it is a function a method...)
assert(params.head.size == 1)
// import ternary operator
import Bool._
// most usual transformation in which the head is a function
def firstTermFunctionTransform = {
// set the recursive transformation context
val recCtx = if (params.tail.size == 1) SinglePar else Par
// go through all possible transformations of functions
(List[Document]() /: transform(params.head.head, App)) {
(list, appIdentifier) =>
// get every possible parameter combination
(list /: getParamsCombinations(params.tail, recCtx)) {
(list2, paramsDoc) =>
list2 :+
group(doParenApp(appIdentifier, paramsDoc))
}
}
}
// match the application definition term
params.head.head match {
case Identifier(_, NormalDeclaration(decl)) => {
// transform the application identifier in an application context
val appIdentifier = transform(params.head.head, App).head
/* check what the declaration says about the application */
// if inheritance function, just recursively transform
if (decl.isInheritanceFun) {
assert(params.size == 2)
return transform(params(1).toList, ctx)
}
// if literal just return simple name
if (decl.isLiteral) {
assert(params.size == 1)
return List(decl.getSimpleName)
}
// constructor call
// NOTE cannot be curried?
if (decl.isConstructor) {
assert(params(1).size == 1)
assert(params(1).head == NullLeaf)
// set if we need parentheses
parenthesesRequired =
// if there are any parameters or ctx is as receiver (App)
params.drop(2).size > 0 || ctx == App
// go through all combinations of parameters documents
return (List[Document]() /: getParamsCombinations(params.drop(2))) {
(list, paramsDoc) =>
list :+
group("new" :/: doParenApp(appIdentifier, paramsDoc))
}
}
// method is on some object
if (decl.belongsToObject) {
assert(params(1).size == 1)
assert(params(1).head == NullLeaf)
// get info about parameters
val paramsInfo = decl.scalaType match {
case Scala.Method(_, params, _) => params
case _ => throw new RuntimeException("Declared method but scala type is not")
}
// set if we need parentheses
parenthesesRequired =
{
// if we have more than one parameter or this term is a single parameter
// to outer application
(params.drop(2).size > 1 || ctx == SinglePar) &&
// and hasParentheses must be false
decl.hasParentheses
}
// go through all combinations of parameters documents
return (List[Document]() /: getParamsCombinations(params.drop(2), paramsInfo, parenthesesRequired)) {
(list, paramsDoc) =>
list :+
// TODO when to generate dot and when not??
//group(decl.getObjectName :: "." :: doParen(appIdentifier, paramsDoc))
group(doParenRecApp(decl.getObjectName, appIdentifier, paramsDoc))
}
}
// TODO refactor - similar to the method construction
if (decl.isField) {
assert(params.size == 2)
// if the field needs this keyword
val needsThis = decl.hasThis
(List[Document]() /: params(1)) {
(listDocsReceivers, receiver) =>
{
// get documents for the receiver objects (or empty if none is needed)
val documentsForThis = {
if (!needsThis)
receiver match {
case Identifier(_, NormalDeclaration(receiverDecl)) if receiverDecl.isThis =>
List(empty)
case _ => transform(receiver, App)
}
else transform(receiver, App)
}
// go through all the receiver objects and add to the list
(listDocsReceivers /: documentsForThis) {
(listDocsTransformedReceiver, receiverDoc) =>
{
listDocsTransformedReceiver :+ group(receiverDoc :?/: appIdentifier)
}
}
}
}
}
else if (!decl.isMethod) {
assert(!decl.isConstructor)
// just a function
//parenthesesRequired = params.tail.size >= 1 || ctx == SinglePar
parenthesesRequired = true
firstTermFunctionTransform
}
else // if (decl.isMethod)
{
// TODO solve parentheses here (with currying)
// get info about parameters
val paramsInfo = decl.scalaType match {
case Scala.Method(_, params, _) => params
case _ => throw new RuntimeException("Declared method but scala type is not")
}
// currying will handle parentheses if needed
parenthesesRequired =
(params.drop(2).size > 1 || ctx == SinglePar || ctx == App) &&
// and hasParentheses must be false
decl.hasParentheses
// if the method needs this keyword
val needsThis = decl.hasThis
(List[Document]() /: params(1)) {
(listDocsReceivers, receiver) =>
{
// get documents for the receiver objects (or empty if none is needed)
val documentsForThis = {
if (!needsThis)
receiver match {
case Identifier(_, NormalDeclaration(receiverDecl)) if receiverDecl.isThis =>
parenthesesRequired = params.drop(2).size >= 1
List(empty)
case _ => transform(receiver, App) // map { (_:Document) :: "." }
}
else transform(receiver, App) // map { (_:Document) :: "." }
}
// go through all the receiver objects and add to the list
(listDocsReceivers /: documentsForThis) {
(listDocsTransformedReceiver, receiverDoc) =>
{
// go through all combinations of parameters documents
(listDocsTransformedReceiver /: getParamsCombinations(params.drop(2), paramsInfo, parenthesesRequired)) {
// and add them to the list
(listDocsTransformedParameters, paramsDoc) =>
listDocsTransformedParameters :+
group(doParenRecApp(receiverDoc, appIdentifier, paramsDoc))
}
}
}
}
}
}
}
// function that is created as an argument or anything else
case Identifier(_, _: AbsDeclaration) | _: Variable | _ =>
firstTermFunctionTransform
}
}
// abstraction first creates all of its arguments
case Abstraction(tpe, vars, subtrees) =>
assert(vars.size > 0)
// check if we need parentheses for variables
parenthesesRequired = vars.size > 1
// for all bodies of this abstraction
val abstractionResults = (List[Document]() /: subtrees) {
(listOfAbstractions, body) =>
{
listOfAbstractions ++
// for all transformations of bodies
(List[Document]() /: transform(body, Expr)) {
(listOfBodies, transformedBody) =>
listOfBodies :+ (
// transform argument variables
doParen(seqToDoc(vars, ", ", { v: Variable => transform(v, Arg).head }))
:/: "=>" :/:
// transform the body
transformedBody)
}
}
}
// return abstraction results
// we need brackets only if this abstraction is parameter and it will not have parentheses
if (ctx == SinglePar)
abstractionResults map { brackets(_: Document) }
else
abstractionResults
}
}
}