@@ -75,9 +75,9 @@ abstract class JSBackend(allowUnresolvedSymbols: Bool) {
75
75
case TyApp (base, _) =>
76
76
translatePattern(base)
77
77
case Inst (bod) => translatePattern(bod)
78
- case _ : Lam | _ : App | _ : Sel | _ : Let | _ : Blk | _ : Bind | _ : Test | _ : With | _ : CaseOf
79
- | _ : Subs | _ : Assign | _ : If | _ : New | NuNew (_) | _ : Splc | _ : Forall | _ : Where
80
- | _ : Super | _ : Eqn | _ : AdtMatchWith | _ : Rft | _ : While =>
78
+ case _ : Lam | _ : App | _ : Sel | _ : Let | _ : Blk | _ : Bind | _ : Test | _ : With | _ : CaseOf | _ : Subs | _ : Assign
79
+ | If (_, _) | New (_, _) | NuNew (_) | _ : Splc | _ : Forall | _ : Where | _ : Super | _ : Eqn | _ : AdtMatchWith
80
+ | _ : Rft | _ : While | _ : Quoted | _ : Unquoted =>
81
81
throw CodeGenError (s " term $t is not a valid pattern " )
82
82
}
83
83
@@ -159,12 +159,12 @@ abstract class JSBackend(allowUnresolvedSymbols: Bool) {
159
159
protected def translateApp (term : App )(implicit scope : Scope ): JSExpr = term match {
160
160
// Binary expressions
161
161
case App (App (Var (op), Tup ((N -> Fld (_, lhs)) :: Nil )), Tup ((N -> Fld (_, rhs)) :: Nil ))
162
- if JSBinary .operators contains op =>
162
+ if oldDefs && ( JSBinary .operators contains op) =>
163
163
JSBinary (op, translateTerm(lhs), translateTerm(rhs))
164
164
// Binary expressions with new-definitions
165
- case App (Var (op), Tup (N -> Fld (_, lhs) :: N -> Fld (_, rhs) :: Nil ))
166
- if JSBinary .operators.contains(op) && ! translateVarImpl(op, isCallee = true ).isRight =>
167
- JSBinary (op , translateTerm(lhs), translateTerm(rhs))
165
+ case App (Var (op), Tup (N -> Fld (_, lhs) :: N -> Fld (_, rhs) :: Nil )) // JS doesn't support operators like `+.` so we need to map them before testing
166
+ if JSBinary .operators.contains(mapFloatingOperator( op)) && ( ! translateVarImpl(op, isCallee = true ).isRight || op =/= mapFloatingOperator(op)) =>
167
+ JSBinary (mapFloatingOperator(op) , translateTerm(lhs), translateTerm(rhs))
168
168
// If-expressions
169
169
case App (App (App (Var (" if" ), Tup ((_, Fld (_, tst)) :: Nil )), Tup ((_, Fld (_, con)) :: Nil )), Tup ((_, Fld (_, alt)) :: Nil )) =>
170
170
JSTenary (translateTerm(tst), translateTerm(con), translateTerm(alt))
@@ -184,6 +184,168 @@ abstract class JSBackend(allowUnresolvedSymbols: Bool) {
184
184
case _ => throw CodeGenError (s " ill-formed application $term" )
185
185
}
186
186
187
+ // * Generate an `App` node for AST constructors
188
+ private def createASTCall (tp : Str , args : Ls [Term ]): App =
189
+ App (Var (tp), Tup (args.map(a => N -> Fld (FldFlags .empty, a))))
190
+
191
+ // * Bound free variables appearing in quasiquotes
192
+ class FreeVars (val vs : Set [Str ])
193
+
194
+ // * Left: the branch is quoted and it has been desugared
195
+ // * Right: the branch is not quoted and quoted subterms have been desugared
196
+ private def desugarQuotedBranch (branch : CaseBranches )(
197
+ implicit scope : Scope , isQuoted : Bool , freeVars : FreeVars
198
+ ): Either [Term , CaseBranches ] = branch match {
199
+ case Case (pat, body, rest) =>
200
+ val dp = desugarQuote(pat)
201
+ val db = desugarQuote(body)
202
+ desugarQuotedBranch(rest) match {
203
+ case L (t) => L (createASTCall(" Case" , dp :: db :: t :: Nil ))
204
+ case R (b) => dp match {
205
+ case dp : SimpleTerm => R (Case (dp, db, b))
206
+ case _ => die
207
+ }
208
+ }
209
+ case Wildcard (body) =>
210
+ if (isQuoted) L (createASTCall(" Wildcard" , desugarQuote(body) :: Nil )) else R (Wildcard (desugarQuote(body)))
211
+ case NoCases => if (isQuoted) L (createASTCall(" NoCases" , Nil )) else R (NoCases )
212
+ }
213
+
214
+ // * Operators `+`, `-`, and `*` will not be available for floating numbers until we have the correct overloading.
215
+ // * Currently, we use OCaml-style floating operators temporarily and translate them into normal JS operators.
216
+ private def mapFloatingOperator (op : Str ) = op match {
217
+ case " +." => " +"
218
+ case " -." => " -"
219
+ case " *." => " *"
220
+ case _ => op
221
+ }
222
+
223
+ // * Desugar `Quoted` into AST constructor invocations.
224
+ // * example 1: `` `42 `` is desugared into `IntLit(42)`
225
+ // * example 2: `` x `=> id(x) `+ `1 `` is desugared into `let x1 = freshName("x") in Lam(Var(x1), App(Var("+"), id(Var(x1)), IntLit(1)))`
226
+ private def desugarQuote (term : Term )(implicit scope : Scope , isQuoted : Bool , freeVars : FreeVars ): Term = term match {
227
+ case Var (name) =>
228
+ val isFreeVar = freeVars.vs(name)
229
+ if (isQuoted || isFreeVar) {
230
+ val runtimeName = scope.resolveValue(name).fold[Str ](
231
+ throw CodeGenError (s " unbound free variable $name is not supported yet. " )
232
+ )(_.runtimeName)
233
+ if (isFreeVar) createASTCall(" Var" , Var (runtimeName) :: Nil ) // quoted variables
234
+ else createASTCall(" Var" , StrLit (runtimeName) :: Nil ) // built-in symbols (e.g., true, error)
235
+ }
236
+ else term
237
+ case lit : IntLit => if (isQuoted) createASTCall(" IntLit" , lit :: Nil ) else lit
238
+ case lit : DecLit => if (isQuoted) createASTCall(" DecLit" , lit :: Nil ) else lit
239
+ case lit : StrLit => if (isQuoted) createASTCall(" StrLit" , lit :: Nil ) else lit
240
+ case lit : UnitLit => if (isQuoted) createASTCall(" UnitLit" , lit :: Nil ) else lit
241
+ case Lam (params, body) =>
242
+ if (isQuoted) {
243
+ val lamScope = scope.derive(" Lam" )
244
+ params match {
245
+ case Tup (params) =>
246
+ val newfreeVars = params.map {
247
+ case N -> Fld (_, Var (nme)) =>
248
+ lamScope.declareParameter(nme)
249
+ nme -> lamScope.declareValue(nme, S (false ), false , N ).runtimeName
250
+ case S (Var (nme)) -> _ =>
251
+ lamScope.declareParameter(nme)
252
+ nme -> lamScope.declareValue(nme, S (false ), false , N ).runtimeName
253
+ case p => throw CodeGenError (s " parameter $p is not supported in quasiquote " )
254
+ }
255
+ newfreeVars.foldRight(desugarQuote(body)(lamScope, isQuoted, new FreeVars (freeVars.vs ++ newfreeVars.map(_._1))))((p, res) =>
256
+ Let (false , Var (p._2), createASTCall(" freshName" , StrLit (p._1) :: Nil ), createASTCall(" Lam" , createASTCall(" Var" , Var (p._2) :: Nil ) :: res :: Nil )))
257
+ case _ => throw CodeGenError (s " term $params is not a valid parameter list " )
258
+ }
259
+ }
260
+ else Lam (params, desugarQuote(body))
261
+ case Unquoted (body) =>
262
+ if (isQuoted) {
263
+ val unquoteScope = scope.derive(" unquote" )
264
+ desugarQuote(body)(unquoteScope, false , freeVars)
265
+ }
266
+ else throw CodeGenError (" unquoted term should be wrapped by quotes." )
267
+ case Quoted (body) =>
268
+ val quoteScope = scope.derive(" quote" )
269
+ val res = desugarQuote(body)(quoteScope, true , freeVars)
270
+ if (isQuoted) throw CodeGenError (" nested quotation is not allowed." )
271
+ else res
272
+ case App (Var (op), Tup (N -> Fld (f1, lhs) :: N -> Fld (f2, rhs) :: Nil ))
273
+ if JSBinary .operators.contains(mapFloatingOperator(op)) && (! translateVarImpl(op, isCallee = true ).isRight || op =/= mapFloatingOperator(op)) =>
274
+ if (isQuoted)
275
+ createASTCall(" App" , createASTCall(" Var" , StrLit (mapFloatingOperator(op)) :: Nil ) :: desugarQuote(lhs) :: desugarQuote(rhs) :: Nil )
276
+ else
277
+ App (Var (op), Tup (N -> Fld (f1, desugarQuote(lhs)) :: N -> Fld (f2, desugarQuote(rhs)) :: Nil ))
278
+ case App (lhs, rhs) =>
279
+ if (isQuoted) createASTCall(" App" , desugarQuote(lhs) :: desugarQuote(rhs) :: Nil )
280
+ else App (desugarQuote(lhs), desugarQuote(rhs))
281
+ case Rcd (fields) =>
282
+ if (isQuoted) createASTCall(" Rcd" , fields.flatMap(f => createASTCall(" Var" , StrLit (f._1.name) :: Nil ) :: desugarQuote(f._2.value) :: Nil ))
283
+ else Rcd (fields.map(f => (f._1, Fld (f._2.flags, desugarQuote(f._2.value)))))
284
+ case Bra (rcd, trm) =>
285
+ if (isQuoted) createASTCall(" Bra" , desugarQuote(trm) :: Nil )
286
+ else Bra (rcd, desugarQuote(trm))
287
+ case Sel (receiver, f @ Var (name)) =>
288
+ if (isQuoted) createASTCall(" Sel" , desugarQuote(receiver) :: createASTCall(" Var" , StrLit (name) :: Nil ) :: Nil )
289
+ else Sel (desugarQuote(receiver), f)
290
+ case Let (rec, Var (name), value, body) =>
291
+ val letScope = scope.derive(" Let" )
292
+ if (isQuoted) {
293
+ letScope.declareParameter(name)
294
+ val freshedName = letScope.declareValue(name, S (false ), false , N ).runtimeName
295
+ Let (false , Var (freshedName), createASTCall(" freshName" , StrLit (name) :: Nil ),
296
+ createASTCall(" Let" , createASTCall(" Var" , Var (freshedName) :: Nil ) :: desugarQuote(value)
297
+ :: desugarQuote(body)(letScope, isQuoted, new FreeVars (freeVars.vs ++ (name :: Nil ))) :: Nil
298
+ ))
299
+ }
300
+ else Let (rec, Var (name), desugarQuote(value), desugarQuote(body)(letScope, isQuoted, freeVars))
301
+ case Blk (stmts) =>
302
+ val blkScope = scope.derive(" blk" )
303
+ val res = stmts.map {
304
+ case t : Term =>
305
+ desugarQuote(t)(blkScope, isQuoted, freeVars)
306
+ case s => throw CodeGenError (s " statement $s is not supported in quasiquotes " )
307
+ }
308
+ if (isQuoted) createASTCall(" Blk" , res)
309
+ else Blk (res)
310
+ case Tup (eles) =>
311
+ def toVar (b : Bool ) = if (b) Var (" true" ) else Var (" false" )
312
+ def toVars (flg : FldFlags ) = toVar(flg.mut) :: toVar(flg.spec) :: toVar(flg.genGetter) :: Nil
313
+ if (isQuoted) createASTCall(" Tup" , eles flatMap {
314
+ case S (Var (name)) -> Fld (flags, t) =>
315
+ createASTCall(" Var" , Var (name) :: Nil ) :: createASTCall(" Fld" , desugarQuote(t) :: toVars(flags)) :: Nil
316
+ case N -> Fld (flags, t) => createASTCall(" Fld" , desugarQuote(t) :: toVars(flags)) :: Nil
317
+ })
318
+ else Tup (eles.map {
319
+ case v -> Fld (flags, t) => v -> Fld (flags, desugarQuote(t))
320
+ })
321
+ case Subs (arr, idx) =>
322
+ if (isQuoted) createASTCall(" Subs" , desugarQuote(arr) :: desugarQuote(idx) :: Nil )
323
+ else Subs (desugarQuote(arr), desugarQuote(idx))
324
+ case Asc (trm, ty) =>
325
+ if (isQuoted) desugarQuote(trm)
326
+ else Asc (desugarQuote(trm), ty)
327
+ case With (lhs, rhs @ Rcd (fields)) =>
328
+ if (isQuoted) createASTCall(" With" , desugarQuote(lhs) :: desugarQuote(rhs) :: Nil )
329
+ else With (desugarQuote(lhs), Rcd (fields.map(f => (f._1, Fld (f._2.flags, desugarQuote(f._2.value))))))
330
+ case CaseOf (trm, cases) =>
331
+ desugarQuotedBranch(cases) match {
332
+ case L (t) => createASTCall(" CaseOf" , desugarQuote(trm) :: t :: Nil )
333
+ case R (b) => CaseOf (desugarQuote(trm), b)
334
+ }
335
+ case _ if term.desugaredTerm.isDefined => desugarQuote(term.desugaredTerm.getOrElse(die))
336
+ case Assign (lhs, rhs) if ! isQuoted => Assign (desugarQuote(lhs), desugarQuote(rhs))
337
+ case NuNew (cls) if ! isQuoted => NuNew (desugarQuote(cls))
338
+ case TyApp (lhs, targs) if ! isQuoted => TyApp (desugarQuote(lhs), targs)
339
+ case Forall (p, body) if ! isQuoted => Forall (p, desugarQuote(body))
340
+ case Inst (body) if ! isQuoted => Inst (desugarQuote(body))
341
+ case _ : Super if ! isQuoted => term
342
+ case Eqn (lhs, rhs) if ! isQuoted => Eqn (lhs, desugarQuote(rhs))
343
+ case While (cond, body) if ! isQuoted => While (desugarQuote(cond), desugarQuote(body))
344
+ case _ : Bind | _ : Test | If (_, _) | _ : Splc | _ : Where | _ : AdtMatchWith | _ : Rft | _ : New
345
+ | _ : Assign | _ : NuNew | _ : TyApp | _ : Forall | _ : Inst | _ : Super | _ : Eqn | _ : While =>
346
+ throw CodeGenError (" this quote syntax is not supported yet." )
347
+ }
348
+
187
349
/**
188
350
* Translate MLscript terms into JavaScript expressions.
189
351
*/
@@ -339,7 +501,10 @@ abstract class JSBackend(allowUnresolvedSymbols: Bool) {
339
501
case TyApp (base, _) => translateTerm(base)
340
502
case Eqn (Var (name), _) =>
341
503
throw CodeGenError (s " assignment of $name is not supported outside a constructor " )
342
- case _ : Bind | _ : Test | If (_, _) | _ : Splc | _ : Where | _ : AdtMatchWith | _ : Rft =>
504
+ case Quoted (body) =>
505
+ val quotedScope = scope.derive(" quote" )
506
+ translateTerm(desugarQuote(body)(quotedScope, true , new FreeVars (Set .empty)))(quotedScope)
507
+ case _ : Bind | _ : Test | If (_, _) | _ : Splc | _ : Where | _ : AdtMatchWith | _ : Rft | _ : Unquoted =>
343
508
throw CodeGenError (s " cannot generate code for term $term" )
344
509
}
345
510
@@ -1265,11 +1430,11 @@ class JSWebBackend extends JSBackend(allowUnresolvedSymbols = true) {
1265
1430
case _ : Def | _ : TypeDef | _ : Constructor =>
1266
1431
throw CodeGenError (" Def and TypeDef are not supported in NewDef files." )
1267
1432
case term : Term =>
1268
- val name = translateTerm(term)(topLevelScope)
1269
- resultNames += name.toSourceCode.toString
1433
+ val res = translateTerm(term)(topLevelScope)
1434
+ resultNames += term.show( true )
1270
1435
topLevelScope.tempVars `with` JSInvoke (
1271
1436
resultsIdent(" push" ),
1272
- name :: Nil
1437
+ res :: Nil
1273
1438
).stmt :: Nil
1274
1439
})
1275
1440
val epilogue = resultsIdent.member(" map" )(JSIdent (prettyPrinterName)).`return` :: Nil
@@ -1289,16 +1454,17 @@ abstract class JSTestBackend extends JSBackend(allowUnresolvedSymbols = false) {
1289
1454
1290
1455
/**
1291
1456
* Generate a piece of code for test purpose. It can be invoked repeatedly.
1457
+ * `prettyPrintQQ` is a temporary hack due to lack of runtime support and should be removed later.
1292
1458
*/
1293
- def apply (pgrm : Pgrm , allowEscape : Bool , isNewDef : Boolean ): JSTestBackend .Result =
1459
+ def apply (pgrm : Pgrm , allowEscape : Bool , isNewDef : Bool , prettyPrintQQ : Bool ): JSTestBackend .Result =
1294
1460
if (! isNewDef)
1295
1461
try generate(pgrm)(topLevelScope, allowEscape) catch {
1296
1462
case e : CodeGenError => JSTestBackend .IllFormedCode (e.getMessage())
1297
1463
case e : UnimplementedError => JSTestBackend .Unimplemented (e.getMessage())
1298
1464
// case NonFatal(e) => JSTestBackend.UnexpectedCrash(e.getClass().getName, e.getMessage())
1299
1465
}
1300
1466
else
1301
- try generateNewDef(pgrm)(topLevelScope, allowEscape) catch {
1467
+ try generateNewDef(pgrm, prettyPrintQQ )(topLevelScope, allowEscape) catch {
1302
1468
case e : CodeGenError => JSTestBackend .IllFormedCode (e.getMessage())
1303
1469
case e : UnimplementedError => JSTestBackend .Unimplemented (e.getMessage())
1304
1470
// case NonFatal(e) => JSTestBackend.UnexpectedCrash(e.getClass().getName, e.getMessage())
@@ -1401,8 +1567,8 @@ abstract class JSTestBackend extends JSBackend(allowUnresolvedSymbols = false) {
1401
1567
JSTestBackend .TestCode (SourceCode .fromStmts(polyfill.emit() ::: prelude).toLines, queries)
1402
1568
}
1403
1569
1404
- private def generateNewDef (pgrm : Pgrm )(implicit scope : Scope , allowEscape : Bool ): JSTestBackend .TestCode = {
1405
-
1570
+ private def generateNewDef (pgrm : Pgrm , prettyPrintQQ : Bool )(implicit scope : Scope , allowEscape : Bool ): JSTestBackend .TestCode = {
1571
+
1406
1572
val (typeDefs, otherStmts) = pgrm.tops.partitionMap {
1407
1573
case _ : Constructor => throw CodeGenError (" unexpected constructor." )
1408
1574
case ot : Terms => R (ot)
@@ -1510,13 +1676,16 @@ abstract class JSTestBackend extends JSBackend(allowUnresolvedSymbols = false) {
1510
1676
1511
1677
// If this is the first time, insert the declaration of `res`.
1512
1678
var prelude : Ls [JSStmt ] = Ls (moduleDecl, insDecl) ::: includes
1513
- if (numRun === 0 )
1679
+ val isFirst = numRun === 0
1680
+ if (isFirst)
1514
1681
prelude = JSLetDecl (lastResultSymbol.runtimeName -> N :: Nil ) :: prelude
1515
1682
1516
1683
// Increase the run number.
1517
1684
numRun = numRun + 1
1518
1685
1519
- JSTestBackend .TestCode (SourceCode .fromStmts(polyfill.emit() ::: prelude).toLines, queries)
1686
+ val qqPredefs =
1687
+ SourceCode (if (isFirst && prettyPrintQQ) QQHelper .prettyPrinter else " " )
1688
+ JSTestBackend .TestCode ((qqPredefs ++ SourceCode .fromStmts(polyfill.emit() ::: prelude)).toLines, queries)
1520
1689
}
1521
1690
}
1522
1691
0 commit comments