firefly-compiler 0.5.6 → 0.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -160,7 +160,8 @@ extend self: JsEmitter {
160
160
  emitLetDefinition(definition: DLet, mutable: Bool, async: Bool): String {
161
161
  let mutability = if(mutable) {"let"} else {"const"}
162
162
  let valueCode = self.emitTerm(definition.value, async)
163
- mutability + " " + escapeKeyword(definition.name) + " = " + valueCode + ";"
163
+ let assignmentCode = if(valueCode != "(void 0)") {" = " + valueCode} else {""}
164
+ mutability + " " + escapeKeyword(definition.name) + assignmentCode + ";"
164
165
  }
165
166
 
166
167
  emitExtendsDefinition(definition: DExtend): String {
@@ -352,80 +353,10 @@ extend self: JsEmitter {
352
353
  let c = if(await) {", $task"} else {""}
353
354
  let call = "(" + self.emitTerm(function, async) + ")(" + self.emitTerm(value, async) + c + ")"
354
355
  if(await) {"(await " + call + ")"} else {call}
355
- | ECall(at, StaticCall(operator, _, _), _, [], [value], _) {!operator.grabFirst().isAsciiLetter()} =>
356
- "(" + operator + self.emitArgument(at, value, async) + ")"
357
- | ECall(at, StaticCall(operator, _, _), _, [], [left, right], _) {!operator.grabFirst().isAsciiLetter()} =>
358
- "(" + self.emitArgument(at, left, async) + " " + operator + " " + self.emitArgument(at, right, async) + ")"
359
- | ECall(at, StaticCall("ff:core/List.List_grab", _, _), _, _, [Argument(_, _, EVariable(_, x1)), Argument(_, _, EVariable(_, x2))], _) =>
360
- "(" + escapeResolved(x1) + "[" + escapeResolved(x2) + "] ?? " +
361
- "ff_core_List.internalGrab_(" + escapeResolved(x1) + ", " + escapeResolved(x2) + "))"
362
- | ECall(at, StaticCall("ff:core/Array.Array_grab", _, _), _, _, [Argument(_, _, EVariable(_, x1)), Argument(_, _, EVariable(_, x2))], _) =>
363
- "(" + escapeResolved(x1) + ".array[" + escapeResolved(x2) + "] ?? " +
364
- "ff_core_Array.internalGrab_(" + escapeResolved(x1) + ", " + escapeResolved(x2) + "))"
365
- | ECall(at, StaticCall("ff:core/UnsafeJs.import", _, _), _, _, [Argument(_, _, EString(_, url))], _) =>
366
- self.jsImporter.add(url.replace("\"", ""))
367
- | ECall(at, StaticCall("ff:core/UnsafeJs.await", _, _), _, _, [Argument(_, _, body)], _) =>
368
- if(async) {
369
- "(await " + self.emitTerm(body, async) + "($task))"
370
- } else {
371
- self.emitTerm(invokeImmediately(body), async)
372
- }
373
- | ECall(at, StaticCall("ff:core/UnsafeJs.cancelled", _, _), _, _, [], _) =>
374
- if(async) {"$task.controller.signal.aborted"} else {"false"}
375
- | ECall(at, StaticCall("ff:core/UnsafeJs.throwIfCancelled", _, _), _, _, [], _) =>
376
- if(async) {"((() => ff_core_Task.Task_throwIfAborted($task))())"} else {""}
377
- | ECall(at, StaticCall("ff:core/UnsafeJs.inAsync", _, _), _, _, [], _) =>
378
- if(self.emittingAsync) {"true"} else {"false"}
379
- | ECall(at, StaticCall("ff:core/UnsafeJs.inBrowser", _, _), _, _, [], _) =>
380
- if(self.emitTarget == EmitBrowser) {"true"} else {"false"}
381
- | ECall(at, StaticCall("ff:core/UnsafeJs.inNode", _, _), _, _, [], _) =>
382
- if(self.emitTarget == EmitNode) {"true"} else {"false"}
383
- | ECall(at, StaticCall("ff:core/UnsafeJs.inBuild", _, _), _, _, [], _) =>
384
- if(self.emitTarget == EmitBuild) {"true"} else {"false"}
385
- | ECall(at, StaticCall("ff:core/UnsafeJs.value", _, _), _, _, [Argument(_, _, e)], _) =>
386
- self.emitTerm(e, async)
387
- | ECall(at, StaticCall("ff:core/UnsafeJs.fromValue", _, _), _, _, [Argument(_, _, e)], _) =>
388
- self.emitTerm(e, async)
389
- | ECall(at, StaticCall("ff:core/Equal.equals", _, _), _, _, [left, right], [Dictionary(_, _, _, typeName, [])]) {
390
- primitiveTypes.contains(typeName) || typeName == "ff:core/Ordering.Ordering"
391
- } =>
392
- "(" + self.emitArgument(at, left, async) + " === " + self.emitArgument(at, right, async) + ")"
393
- | ECall(at, StaticCall("ff:core/Equal.notEquals", _, _), _, _, [left, right], [Dictionary(_, _, _, typeName, [])]) {
394
- primitiveTypes.contains(typeName) || typeName == "ff:core/Ordering.Ordering"
395
- } =>
396
- "(" + self.emitArgument(at, left, async) + " !== " + self.emitArgument(at, right, async) + ")"
397
- | ECall(at, StaticCall("ff:core/Ordering.before", _, _), _, _, [left, right], [Dictionary(_, _, _, typeName, [])]) {
398
- primitiveTypes.contains(typeName)
399
- } =>
400
- "(" + self.emitArgument(at, left, async) + " < " + self.emitArgument(at, right, async) + ")"
401
- | ECall(at, StaticCall("ff:core/Ordering.notBefore", _, _), _, _, [left, right], [Dictionary(_, _, _, typeName, [])]) {
402
- primitiveTypes.contains(typeName)
403
- } =>
404
- "(" + self.emitArgument(at, left, async) + " >= " + self.emitArgument(at, right, async) + ")"
405
- | ECall(at, StaticCall("ff:core/Ordering.after", _, _), _, _, [left, right], [Dictionary(_, _, _, typeName, [])]) {
406
- primitiveTypes.contains(typeName)
407
- } =>
408
- "(" + self.emitArgument(at, left, async) + " > " + self.emitArgument(at, right, async) + ")"
409
- | ECall(at, StaticCall("ff:core/Ordering.notAfter", _, _), _, _, [left, right], [Dictionary(_, _, _, typeName, [])]) {
410
- primitiveTypes.contains(typeName)
356
+ | ECall(at, StaticCall(name, _, _), _, _, arguments, dictionaries) {
357
+ self.emitSpecialCall(term, async, name, arguments.map {_.value}, dictionaries) | Some(code)
411
358
  } =>
412
- "(" + self.emitArgument(at, left, async) + " <= " + self.emitArgument(at, right, async) + ")"
413
- | ECall(_, StaticCall("ff:core/List.fillBy", _, _), effect, _, [size, Argument(_, _, ELambda(at,
414
- Lambda(_, _, [MatchCase(_, [PVariable(_, name)], [], body)@c])@l
415
- ))], _) {
416
- !effectTypeIsAsync(effect)
417
- } =>
418
- let n = name.map {escapeResolved(_)}.else {"i"}
419
- let newAsync = self.emittingAsync && effectTypeIsAsync(effect)
420
- let await = if(newAsync) {"await "} else {""}
421
- await + "((() => {\n" +
422
- "const size = " + self.emitArgument(at, size, async) + ";\n" + // Not correct if async and body isn't
423
- "const result = [];\n" +
424
- "for(let " + n + " = 0; " + n + " < size; " + n + "++) {\n" +
425
- "result.push(" + self.emitTerm(body, newAsync) + ");\n" +
426
- "}\n" +
427
- "return result;\n" +
428
- "})())"
359
+ code
429
360
  | ECall(at, StaticCall(name, _, True), effect, typeArguments, arguments, dictionaries) =>
430
361
  let await = async && effectTypeIsAsync(effect)
431
362
  let dictionaryStrings = dictionaries.map {self.emitDictionary(_)}
@@ -485,6 +416,13 @@ extend self: JsEmitter {
485
416
  | _ =>
486
417
  "(function() {\n" + self.emitStatements(term, True, async) + "\n})()"
487
418
  }}
419
+
420
+ emitField(term: Term, async: Bool, dot: String = "."): String {
421
+ term.{
422
+ | EString(_, q) {safeBare(q) | Some(s)} => dot + s
423
+ | _ => "[" + self.emitTerm(term, async) + "]"
424
+ }
425
+ }
488
426
 
489
427
  emitDictionary(d: Dictionary): String {
490
428
  let m = if(d.moduleName != "") {
@@ -522,46 +460,6 @@ extend self: JsEmitter {
522
460
  | EAssignField(at, operator, record, field, value) =>
523
461
  self.emitTerm(record, async) + "." + escapeKeyword(field) + " " + operator + "= " +
524
462
  self.emitTerm(value, async)
525
- | ECall(at, StaticCall("ff:core/Core.while", _, _), _, _, [condition, body], _) =>
526
- "while(" + self.emitTerm(invokeImmediately(condition.value), async) + ") {\n" +
527
- self.emitStatements(invokeImmediately(body.value), False, async) + "\n}"
528
- | ECall(at, StaticCall("ff:core/Core.doWhile", _, _), _, _, [Argument(_, _, doWhileBody)], _) {
529
- invokeImmediately(doWhileBody) | ESequential(_, body, condition)
530
- } =>
531
- "while(true) {\n" +
532
- self.emitStatements(body, False, async) +
533
- "\nif(!" + self.emitTerm(condition, async) + ") break" +
534
- "\n}"
535
- | ECall(at, StaticCall("ff:core/Core.doWhile", _, _), _, _, [Argument(_, _, doWhileBody)], _) {
536
- invokeImmediately(doWhileBody) | body
537
- } =>
538
- "while(" + self.emitTerm(body, async) + ") {}"
539
- | ECall(at, StaticCall("ff:core/Core.if", _, _), _, _, [condition, body], _) =>
540
- "if(" + self.emitTerm(condition.value, async) + ") {\n" +
541
- if(last) {
542
- "return ff_core_Option.Some(" + self.emitTerm(invokeImmediately(body.value), async) +
543
- ")\n} else return ff_core_Option.None()"
544
- } else {
545
- self.emitStatements(invokeImmediately(body.value), False, async) + "\n}"
546
- }
547
- | ECall(at, StaticCall("ff:core/Core.throw", _, _), _, _, [argument], [dictionary]) =>
548
- let d = self.emitDictionary(dictionary)
549
- let a = self.emitArgument(at, argument, async)
550
- "throw Object.assign(new Error(), {ffException: ff_core_Any.toAny_(" + a + ", " + d + ")})"
551
- | ECall(at, StaticCall("ff:core/Try.Try_catch", _, _), _, _, _, _) {
552
- self.emitTryCatchFinally(term, last, async) | Some(code)
553
- } =>
554
- code
555
- | ECall(at, StaticCall("ff:core/Try.Try_catchAny", _, _), _, _, _, _) {
556
- self.emitTryCatchFinally(term, last, async) | Some(code)
557
- } =>
558
- code
559
- | ECall(at, StaticCall("ff:core/Try.Try_finally", _, _), _, _, _, _) {
560
- self.emitTryCatchFinally(term, last, async) | Some(code)
561
- } =>
562
- code
563
- | ECall(at, StaticCall("ff:core/UnsafeJs.throwIfCancelled", _, _), _, _, [], _) =>
564
- if(async) {"ff_core_Task.Task_throwIfAborted($task)"} else {""}
565
463
  | ECall(at, StaticCall(name, True, instanceCall), effect, _, arguments, _) =>
566
464
  if(instanceCall) {throw(CompileError(at, "Not yet implemented: Tail calls on trait methods."))}
567
465
  self.tailCallUsed = True
@@ -572,6 +470,10 @@ extend self: JsEmitter {
572
470
  ))
573
471
  }.collect {_}.unzip()
574
472
  "{\n" + pair.first.join("\n") + "\n" + pair.second.join("\n") + "\ncontinue _tailcall\n}"
473
+ | ECall(at, StaticCall(name, _, _), _, _, arguments, dictionaries) {
474
+ self.emitSpecialStatement(term, last, async, name, arguments.map {_.value}, dictionaries) | Some(code)
475
+ } =>
476
+ code
575
477
  | EPipe(at, value, _, ELambda(_, Lambda(_, _, cases))) =>
576
478
  Patterns.convertAndCheck(self.otherModules, cases)
577
479
  if(!last) {"do "}.else {""} +
@@ -601,6 +503,221 @@ extend self: JsEmitter {
601
503
  }
602
504
  }
603
505
  }
506
+
507
+ emitSpecialCall(
508
+ term: Term
509
+ async: Bool
510
+ name: String
511
+ arguments: List[Term]
512
+ dictionaries: List[Dictionary]
513
+ ): Option[String] {
514
+ name.{
515
+ | operator {!operator.grabFirst().isAsciiLetter()} {arguments | [value]} =>
516
+ Some("(" + operator + self.emitTerm(value, async) + ")")
517
+ | operator {!operator.grabFirst().isAsciiLetter()} {arguments | [left, right]} =>
518
+ Some("(" + self.emitTerm(left, async) + " " + operator + " " + self.emitTerm(right, async) + ")")
519
+ | "ff:core/List.List_grab" {arguments | [EVariable(_, x1), EVariable(_, x2)]} =>
520
+ Some(
521
+ "(" + escapeResolved(x1) + "[" + escapeResolved(x2) + "] ?? " +
522
+ "ff_core_List.internalGrab_(" + escapeResolved(x1) + ", " + escapeResolved(x2) + "))"
523
+ )
524
+ | "ff:core/Array.Array_grab" {arguments | [EVariable(_, x1), EVariable(_, x2)]} =>
525
+ Some(
526
+ "(" + escapeResolved(x1) + ".array[" + escapeResolved(x2) + "] ?? " +
527
+ "ff_core_Array.internalGrab_(" + escapeResolved(x1) + ", " + escapeResolved(x2) + "))"
528
+ )
529
+ | "ff:core/Equal.equals" {arguments | [left, right]} {dictionaries | [Dictionary(_, _, _, typeName, [])]} {
530
+ primitiveTypes.contains(typeName) || typeName == "ff:core/Ordering.Ordering"
531
+ } =>
532
+ Some("(" + self.emitTerm(left, async) + " === " + self.emitTerm(right, async) + ")")
533
+ | "ff:core/Equal.notEquals" {arguments | [left, right]} {dictionaries | [Dictionary(_, _, _, typeName, [])]} {
534
+ primitiveTypes.contains(typeName) || typeName == "ff:core/Ordering.Ordering"
535
+ } =>
536
+ Some("(" + self.emitTerm(left, async) + " !== " + self.emitTerm(right, async) + ")")
537
+ | "ff:core/Ordering.before" {arguments | [left, right]} {dictionaries | [Dictionary(_, _, _, typeName, [])]} {
538
+ primitiveTypes.contains(typeName)
539
+ } =>
540
+ Some("(" + self.emitTerm(left, async) + " < " + self.emitTerm(right, async) + ")")
541
+ | "ff:core/Ordering.notBefore" {arguments | [left, right]} {dictionaries | [Dictionary(_, _, _, typeName, [])]} {
542
+ primitiveTypes.contains(typeName)
543
+ } =>
544
+ Some("(" + self.emitTerm(left, async) + " >= " + self.emitTerm(right, async) + ")")
545
+ | "ff:core/Ordering.after" {arguments | [left, right]} {dictionaries | [Dictionary(_, _, _, typeName, [])]} {
546
+ primitiveTypes.contains(typeName)
547
+ } =>
548
+ Some("(" + self.emitTerm(left, async) + " > " + self.emitTerm(right, async) + ")")
549
+ | "ff:core/Ordering.notAfter" {arguments | [left, right]} {dictionaries | [Dictionary(_, _, _, typeName, [])]} {
550
+ primitiveTypes.contains(typeName)
551
+ } =>
552
+ Some("(" + self.emitTerm(left, async) + " <= " + self.emitTerm(right, async) + ")")
553
+ | "ff:core/List.fillBy" {term | ECall call} {arguments | [size, ELambda(at,
554
+ Lambda(_, _, [MatchCase(_, [PVariable(_, name)], [], body)@c])@l
555
+ )]} {
556
+ !effectTypeIsAsync(call.effect)
557
+ } =>
558
+ let n = name.map {escapeResolved(_)}.else {"i"}
559
+ let newAsync = self.emittingAsync && effectTypeIsAsync(call.effect)
560
+ let await = if(newAsync) {"await "} else {""}
561
+ Some(
562
+ await + "((() => {\n" +
563
+ "const size = " + self.emitTerm(size, async) + ";\n" + // Not correct if async and body isn't
564
+ "const result = [];\n" +
565
+ "for(let " + n + " = 0; " + n + " < size; " + n + "++) {\n" +
566
+ "result.push(" + self.emitTerm(body, newAsync) + ");\n" +
567
+ "}\n" +
568
+ "return result;\n" +
569
+ "})())"
570
+ )
571
+ | "ff:core/UnsafeJs.import" {arguments | [EString(_, url)]} =>
572
+ Some(self.jsImporter.add(url.replace("\"", "")))
573
+ | "ff:core/UnsafeJs.await" {arguments | [body]} =>
574
+ if(async) {
575
+ Some("(await " + self.emitTerm(body, async) + "($task))")
576
+ } else {
577
+ Some(self.emitTerm(invokeImmediately(body), async))
578
+ }
579
+ | "ff:core/UnsafeJs.cancelled" =>
580
+ Some(if(async) {"$task.controller.signal.aborted"} else {"false"})
581
+ | "ff:core/UnsafeJs.throwIfCancelled" =>
582
+ Some(if(async) {"((() => ff_core_Task.Task_throwIfAborted($task))())"} else {""})
583
+ | "ff:core/UnsafeJs.inAsync" =>
584
+ Some(if(self.emittingAsync) {"true"} else {"false"})
585
+ | "ff:core/UnsafeJs.inBrowser" =>
586
+ Some(if(self.emitTarget == EmitBrowser) {"true"} else {"false"})
587
+ | "ff:core/UnsafeJs.inNode" =>
588
+ Some(if(self.emitTarget == EmitNode) {"true"} else {"false"})
589
+ | "ff:core/UnsafeJs.inBuild" =>
590
+ Some(if(self.emitTarget == EmitBuild) {"true"} else {"false"})
591
+ | "ff:core/UnsafeJs.value" {arguments | [e]} =>
592
+ Some(self.emitTerm(e, async))
593
+ | "ff:core/UnsafeJs.fromValue" {arguments | [e]} =>
594
+ Some(self.emitTerm(e, async))
595
+ | "ff:core/JsValue.JsValue_get" {arguments | [e1, e2]} =>
596
+ Some(self.emitTerm(e1, async) + self.emitField(e2, async))
597
+ | name {name.removeFirst("ff:core/JsValue.JsValue_call") | Some(n)} {n.all {_.isAsciiDigit()}} {
598
+ arguments | [e1, e2, ...es]
599
+ } =>
600
+ let argumentCode = es.map {self.emitTerm(_, async)}.join(", ")
601
+ Some(self.emitTerm(e1, async) + self.emitField(e2, async) + "(" + argumentCode + ")")
602
+ | name {name.removeFirst("ff:core/JsValue.JsValue_callValue") | Some(n)} {n.all {_.isAsciiDigit()}} {
603
+ arguments | [e1, ...es]
604
+ } =>
605
+ let argumentCode = es.map {self.emitTerm(_, async)}.join(", ")
606
+ Some(self.emitTerm(e1, async) + "(" + argumentCode + ")")
607
+ | name {name.removeFirst("ff:core/JsValue.JsValue_new") | Some(n)} {n.all {_.isAsciiDigit()}} {
608
+ arguments | [e1, ...es]
609
+ } =>
610
+ let argumentCode = es.map {self.emitTerm(_, async)}.join(", ")
611
+ Some("(new " + self.emitTerm(e1, async) + "(" + argumentCode + ")" + ")")
612
+ | "ff:core/JsValue.JsValue_with" =>
613
+ function go(e: Term, fields: List[Pair[Term, Term]]): String {
614
+ e.{
615
+ | ECall(_, StaticCall("ff:core/JsValue.JsValue_with", _, _), _, _, [a1, a2, a3], _) =>
616
+ go(a1.value, [Pair(a2.value, a3.value), ...fields])
617
+ | ECall(_, StaticCall(name, _, _), _, _, [a], _) {
618
+ name == "ff:core/JsSystem.JsSystem_object" || name == "ff:core/JsSystem.JsSystem_new0"
619
+ } {
620
+ noSideEffects(a.value)
621
+ } =>
622
+ "{" + fields.map {p =>
623
+ self.emitField(p.first, async, dot = "") + ": " + self.emitTerm(p.second, async)
624
+ }.join(", ") + "}"
625
+ | _ =>
626
+ "{..." + self.emitTerm(e, async) + ", " + fields.map {p =>
627
+ self.emitField(p.first, async, dot = "") + ": " + self.emitTerm(p.second, async)
628
+ }.join(", ") + "}"
629
+ }
630
+ }
631
+ Some(go(term, []))
632
+ | name {name.removeFirst("ff:core/JsSystem.JsSystem_call") | Some(n)} {n.all {_.isAsciiDigit()}} {
633
+ arguments | [e1, EString(_, q)@e2, ...es]
634
+ } {noSideEffects(e1)} =>
635
+ let argumentCode = es.map {self.emitTerm(_, async)}.join(", ")
636
+ Some(safeBare(q).else {"globalThis[" + self.emitTerm(e2, async) + "]"} + "(" + argumentCode + ")")
637
+ | name {name.removeFirst("ff:core/JsSystem.JsSystem_function") | Some(n)} {n.all {_.isAsciiDigit()}} {
638
+ arguments | [e1, e2]
639
+ } {noSideEffects(e1)} {term | ECall call} {!effectTypeIsAsync(call.effect)} =>
640
+ Some(self.emitTerm(e2, async))
641
+ | "ff:core/JsSystem.JsSystem_get" {arguments | [e1, EString(_, q)@e2]} {noSideEffects(e1)} =>
642
+ Some(safeBare(q).else {"globalThis[" + self.emitTerm(e2, async) + "]"})
643
+ | "ff:core/JsSystem.JsSystem_object" {arguments | [e]} {noSideEffects(e)} =>
644
+ Some("{}")
645
+ | "ff:core/JsSystem.JsSystem_new0" {arguments | [e]} {noSideEffects(e)} =>
646
+ Some("{}")
647
+ | "ff:core/JsSystem.JsSystem_null" {arguments | [e]} {noSideEffects(e)} =>
648
+ Some("null")
649
+ | "ff:core/JsSystem.JsSystem_undefined" {arguments | [e]} {noSideEffects(e)} =>
650
+ Some("(void 0)")
651
+ | _ =>
652
+ None
653
+ }
654
+ }
655
+
656
+ emitSpecialStatement(
657
+ term: Term
658
+ last: Bool
659
+ async: Bool
660
+ name: String
661
+ arguments: List[Term]
662
+ dictionaries: List[Dictionary]
663
+ ): Option[String] {
664
+ name.{
665
+ | "ff:core/Core.while" {arguments | [condition, body]} =>
666
+ Some(
667
+ "while(" + self.emitComma(invokeImmediately(condition), async) + ") {\n" +
668
+ self.emitStatements(invokeImmediately(body), False, async) + "\n}"
669
+ )
670
+ | "ff:core/Core.doWhile" {arguments | [doWhileBody]} {
671
+ invokeImmediately(doWhileBody) | ESequential(_, body, condition)
672
+ } =>
673
+ Some(
674
+ "while(true) {\n" +
675
+ self.emitStatements(body, False, async) +
676
+ "\nif(!" + self.emitComma(condition, async) + ") break" +
677
+ "\n}"
678
+ )
679
+ | "ff:core/Core.doWhile" {arguments | [doWhileBody]} {
680
+ invokeImmediately(doWhileBody) | body
681
+ } =>
682
+ Some("while(" + self.emitComma(body, async) + ") {}")
683
+ | "ff:core/Core.if" {arguments | [condition, body]} =>
684
+ Some(
685
+ "if(" + self.emitComma(condition, async) + ") {\n" +
686
+ if(last) {
687
+ "return ff_core_Option.Some(" + self.emitTerm(invokeImmediately(body), async) +
688
+ ")\n} else return ff_core_Option.None()"
689
+ } else {
690
+ self.emitStatements(invokeImmediately(body), False, async) + "\n}"
691
+ }
692
+ )
693
+ | "ff:core/Core.throw" {term | ECall c} {c.arguments | [argument]} {dictionaries | [dictionary]} =>
694
+ let d = self.emitDictionary(dictionary)
695
+ let a = self.emitArgument(term.at, argument, async)
696
+ Some("throw Object.assign(new Error(), {ffException: ff_core_Any.toAny_(" + a + ", " + d + ")})")
697
+ | "ff:core/Try.Try_catch" {self.emitTryCatchFinally(term, last, async) | Some(code)} =>
698
+ Some(code)
699
+ | "ff:core/Try.Try_catchAny" {self.emitTryCatchFinally(term, last, async) | Some(code)} =>
700
+ Some(code)
701
+ | "ff:core/Try.Try_finally" {self.emitTryCatchFinally(term, last, async) | Some(code)} =>
702
+ Some(code)
703
+ | "ff:core/UnsafeJs.throwIfCancelled" =>
704
+ Some(if(async) {"ff_core_Task.Task_throwIfAborted($task)"} else {""})
705
+ | "ff:core/JsValue.JsValue_set" {arguments | [e1, e2, e3]} =>
706
+ Some(self.emitTerm(e1, async) + self.emitField(e2, async) + " = " + self.emitTerm(e3, async))
707
+ | "ff:core/JsValue.JsValue_increment" {arguments | [e1, e2, e3]} =>
708
+ Some(self.emitTerm(e1, async) + self.emitField(e2, async) + " += " + self.emitTerm(e3, async))
709
+ | "ff:core/JsValue.JsValue_decrement" {arguments | [e1, e2, e3]} =>
710
+ Some(self.emitTerm(e1, async) + self.emitField(e2, async) + " -= " + self.emitTerm(e3, async))
711
+ | "ff:core/JsSystem.JsSystem_set" {arguments | [e1, EString(_, q), e3]} {noSideEffects(e1)} {safeBare(q) | Some(s)} =>
712
+ Some(s + " = " + self.emitTerm(e3, async))
713
+ | "ff:core/JsSystem.JsSystem_increment" {arguments | [e1, EString(_, q), e3]} {noSideEffects(e1)} {safeBare(q) | Some(s)} =>
714
+ Some(s + " += " + self.emitTerm(e3, async))
715
+ | "ff:core/JsSystem.JsSystem_decrement" {arguments | [e1, EString(_, q), e3]} {noSideEffects(e1)} {safeBare(q) | Some(s)} =>
716
+ Some(s + " -= " + self.emitTerm(e3, async))
717
+ | _ =>
718
+ None
719
+ }
720
+ }
604
721
 
605
722
  emitTryCatchFinally(term: Term, last: Bool, async: Bool): Option[String] {
606
723
  function emitCatch(catchEffect: Type, cases: List[MatchCase]): String {
@@ -866,6 +983,22 @@ extend self: JsEmitter {
866
983
  self.emitTerm(value, async)
867
984
  }
868
985
  }
986
+
987
+ emitComma(term: Term, async: Bool): String {
988
+ term.{
989
+ | ESequential(_, ESequential(_, before1, before2), after) {
990
+ safeCommable(before1) && safeCommable(before2)
991
+ } =>
992
+ "(" + self.emitStatements(before1, False, async) + ", " +
993
+ self.emitStatements(before2, False, async) + ", " +
994
+ self.emitTerm(after, async) + ")"
995
+ | ESequential(_, before, after) {safeCommable(before)} =>
996
+ "(" + self.emitStatements(before, False, async) + ", " +
997
+ self.emitTerm(after, async) + ")"
998
+ | _ =>
999
+ self.emitTerm(term, async)
1000
+ }
1001
+ }
869
1002
 
870
1003
  }
871
1004
 
@@ -899,6 +1032,21 @@ invokeImmediately(function: Term): Term {
899
1032
  ECall(function.at, DynamicCall(function, False), effect, [], [], [])
900
1033
  }
901
1034
 
1035
+ safeCommable(term: Term): Bool {
1036
+ term.{
1037
+ | EField _ => True
1038
+ | EVariable _ => True
1039
+ | EAssign _ => True
1040
+ | EAssignField _ => True
1041
+ | ECall _ => True
1042
+ | EString(_, _) => True
1043
+ | EInt(_, _) => True
1044
+ | EChar(_, _) => True
1045
+ | EFloat(_, _) => True
1046
+ | _ => False
1047
+ }
1048
+ }
1049
+
902
1050
  extractTypeName(type: Type): String {
903
1051
  | TVariable(at, index) =>
904
1052
  fail(at, "Unexpected type variable: $" + index)
@@ -946,6 +1094,29 @@ effectTypeIsAsync(effect: Type): Bool {
946
1094
  | _ => False
947
1095
  }
948
1096
 
1097
+ safeBare(quotedString: String): Option[String] {
1098
+ // TODO: And not a reserved word in JS
1099
+ quotedString.removeFirst("\"").flatMap {_.removeLast("\"")}.filter {s =>
1100
+ s.first().any {_.isAsciiLetter()} && s.all {_.isAsciiLetterOrDigit()}
1101
+ }
1102
+ }
1103
+
1104
+ noSideEffects(term: Term): Bool {
1105
+ term.{
1106
+ | EField(_, _, e, _) => noSideEffects(e)
1107
+ | EVariable(_, _) => True
1108
+ | ECall(_, StaticCall("ff:core/BrowserSystem.BrowserSystem_js", _, _), _, _, [a], _) => noSideEffects(a.value)
1109
+ | ECall(_, StaticCall("ff:core/BuildSystem.BuildSystem_js", _, _), _, _, [a], _) => noSideEffects(a.value)
1110
+ | ECall(_, StaticCall("ff:core/NodeSystem.NodeSystem_js", _, _), _, _, [a], _) => noSideEffects(a.value)
1111
+ | ECall(_, StaticCall("ff:core/UnsafeJs.jsSystem", _, _), _, _, _, _) => True
1112
+ | EString(_, _) => True
1113
+ | EInt(_, _) => True
1114
+ | EChar(_, _) => True
1115
+ | EFloat(_, _) => True
1116
+ | _ => False
1117
+ }
1118
+ }
1119
+
949
1120
  primitiveTypes = [
950
1121
  "ff:core/Bool.Bool"
951
1122
  "ff:core/Char.Char"
@@ -1135,8 +1135,8 @@ extend self: Parser {
1135
1135
  }
1136
1136
 
1137
1137
  parseUnary(): Term {
1138
- if(self.current().is(LOperator)) {
1139
- let token = self.skip(LOperator)
1138
+ if(self.current().is2(LUnary, LOperator)) {
1139
+ let token = if(self.current().is(LUnary)) {self.skip(LUnary)} else {self.skip(LOperator)}
1140
1140
  let term = self.parseUnary()
1141
1141
  let effect = self.freshUnificationVariable(token.at())
1142
1142
  let target = DynamicCall(EVariable(token.at(), token.raw()), False)
@@ -1152,7 +1152,7 @@ extend self: Parser {
1152
1152
  True
1153
1153
  } else {False}
1154
1154
  mutable result = self.parseAtom()
1155
- while {self.current().is4(LBracketLeft, LColon, LDot, LArrowThin) || self.current().rawIs2("!", "?")} {
1155
+ while {self.current().is5(LBracketLeft, LColon, LDot, LArrowThin, LUnary)} {
1156
1156
  if(self.current().is(LDot)) {
1157
1157
  self.skip(LDot)
1158
1158
  if(self.current().rawIs("{")) {
@@ -1167,8 +1167,8 @@ extend self: Parser {
1167
1167
  }
1168
1168
  } elseIf {self.current().is(LArrowThin)} {
1169
1169
  result = self.parseDynamicMember(result)
1170
- } elseIf {self.current().rawIs2("!", "?")} {
1171
- let token = self.skip(LOperator)
1170
+ } elseIf {self.current().is(LUnary)} {
1171
+ let token = self.skip(LUnary)
1172
1172
  let method = if(token.rawIs("!")) {"ff:core/UnsafeJs.value"} else {"ff:core/UnsafeJs.fromValue"}
1173
1173
  let target = DynamicCall(EVariable(token.at(), method), False)
1174
1174
  let effect = self.freshUnificationVariable(token.at())
package/compiler/Token.ff CHANGED
@@ -42,6 +42,10 @@ extend token: Token {
42
42
  token.kind == kind1 || token.kind == kind2 || token.kind == kind3 || token.kind == kind4
43
43
  }
44
44
 
45
+ is5(kind1: TokenKind, kind2: TokenKind, kind3: TokenKind, kind4: TokenKind, kind5: TokenKind): Bool {
46
+ token.kind == kind1 || token.kind == kind2 || token.kind == kind3 || token.kind == kind4 || token.kind == kind5
47
+ }
48
+
45
49
  rawIs(value: String): Bool {
46
50
  token.stopOffset - token.startOffset == value.size() &&
47
51
  token.code.startsWith(value, token.startOffset)
@@ -75,6 +79,7 @@ data TokenKind {
75
79
  LBracketLeft
76
80
  LBracketRight
77
81
  LOperator
82
+ LUnary
78
83
  LComma
79
84
  LSeparator
80
85
  LDot
@@ -105,6 +110,7 @@ extend self: TokenKind {
105
110
  | LWildcard => True
106
111
  | LBracketLeft => False
107
112
  | LBracketRight => True
113
+ | LUnary => True
108
114
  | LOperator => False
109
115
  | LComma => False
110
116
  | LSeparator => False
@@ -135,6 +141,7 @@ extend self: TokenKind {
135
141
  | LWildcard => True
136
142
  | LBracketLeft => True
137
143
  | LBracketRight => False
144
+ | LUnary => True
138
145
  | LOperator => False
139
146
  | LComma => False
140
147
  | LSeparator => False
@@ -165,6 +172,7 @@ extend self: TokenKind {
165
172
  | LWildcard => True
166
173
  | LBracketLeft => False
167
174
  | LBracketRight => False
175
+ | LUnary => False
168
176
  | LOperator => False
169
177
  | LComma => False
170
178
  | LSeparator => False
@@ -205,12 +205,12 @@ tokenize(file: String, code: String, completionAt: Option[Location], attemptFixe
205
205
  emitToken(LComma, start, i)
206
206
 
207
207
  } elseIf {
208
- (code.grab(i) == '!' || code.grab(i) == '?') && i + 1 < code.size() &&
209
- (code.grab(i + 1) == '.' || code.grab(i + 1) == '-')
208
+ code.grab(i) == '?' ||
209
+ (code.grab(i) == '!' && (i + 1 >= code.size() || code.grab(i + 1) != '='))
210
210
  } {
211
211
 
212
212
  i += 1
213
- emitToken(LOperator, start, i)
213
+ emitToken(LUnary, start, i)
214
214
 
215
215
  } elseIf {operatorCharacters.contains(code.grab(i))} {
216
216
 
package/core/JsValue.ff CHANGED
@@ -116,38 +116,37 @@ extend self: JsValue {
116
116
  target js sync "for(const value of self_) if(!body_(value)) break"
117
117
  target js async "for(const value of self_) if(!await body_(value, $task)) break"
118
118
 
119
-
120
- call(name: String, arguments: List[JsValue]): JsValue
119
+ call[A0: IsJsValue](name: A0, arguments: List[JsValue]): JsValue
121
120
  target js sync "return self_[name_].apply(this_, arguments_)"
122
121
 
123
- call0(name: String): JsValue
122
+ call0[A0: IsJsValue](name: A0): JsValue
124
123
  target js sync "return self_[name_].call(self_)"
125
124
 
126
- call1[A1: IsJsValue](name: String, a1: A1): JsValue
125
+ call1[A0: IsJsValue, A1: IsJsValue](name: A0, a1: A1): JsValue
127
126
  target js sync "return self_[name_].call(self_, a1_)"
128
127
 
129
- call2[A1: IsJsValue, A2: IsJsValue](name: String, a1: A1, a2: A2): JsValue
128
+ call2[A0: IsJsValue, A1: IsJsValue, A2: IsJsValue](name: A0, a1: A1, a2: A2): JsValue
130
129
  target js sync "return self_[name_].call(self_, a1_, a2_)"
131
130
 
132
- call3[A1: IsJsValue, A2: IsJsValue, A3: IsJsValue](name: String, a1: A1, a2: A2, a3: A3): JsValue
131
+ call3[A0: IsJsValue, A1: IsJsValue, A2: IsJsValue, A3: IsJsValue](name: A0, a1: A1, a2: A2, a3: A3): JsValue
133
132
  target js sync "return self_[name_].call(self_, a1_, a2_, a3_)"
134
133
 
135
- call4[A1: IsJsValue, A2: IsJsValue, A3: IsJsValue, A4: IsJsValue](name: String, a1: A1, a2: A2, a3: A3, a4: A4): JsValue
134
+ call4[A0: IsJsValue, A1: IsJsValue, A2: IsJsValue, A3: IsJsValue, A4: IsJsValue](name: A0, a1: A1, a2: A2, a3: A3, a4: A4): JsValue
136
135
  target js sync "return self_[name_].call(self_, a1_, a2_, a3_, a4_)"
137
136
 
138
- call5[A1: IsJsValue, A2: IsJsValue, A3: IsJsValue, A4: IsJsValue, A5: IsJsValue](name: String, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5): JsValue
137
+ call5[A0: IsJsValue, A1: IsJsValue, A2: IsJsValue, A3: IsJsValue, A4: IsJsValue, A5: IsJsValue](name: A0, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5): JsValue
139
138
  target js sync "return self_[name_].call(self_, a1_, a2_, a3_, a4_, a5_)"
140
139
 
141
- call6[A1: IsJsValue, A2: IsJsValue, A3: IsJsValue, A4: IsJsValue, A5: IsJsValue, A6: IsJsValue](name: String, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6): JsValue
140
+ call6[A0: IsJsValue, A1: IsJsValue, A2: IsJsValue, A3: IsJsValue, A4: IsJsValue, A5: IsJsValue, A6: IsJsValue](name: A0, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6): JsValue
142
141
  target js sync "return self_[name_].call(self_, a1_, a2_, a3_, a4_, a5_, a6_)"
143
142
 
144
- call7[A1: IsJsValue, A2: IsJsValue, A3: IsJsValue, A4: IsJsValue, A5: IsJsValue, A6: IsJsValue, A7: IsJsValue](name: String, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7): JsValue
143
+ call7[A0: IsJsValue, A1: IsJsValue, A2: IsJsValue, A3: IsJsValue, A4: IsJsValue, A5: IsJsValue, A6: IsJsValue, A7: IsJsValue](name: A0, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7): JsValue
145
144
  target js sync "return self_[name_].call(self_, a1_, a2_, a3_, a4_, a5_, a6_, a7_)"
146
145
 
147
- call8[A1: IsJsValue, A2: IsJsValue, A3: IsJsValue, A4: IsJsValue, A5: IsJsValue, A6: IsJsValue, A7: IsJsValue, A8: IsJsValue](name: String, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8): JsValue
146
+ call8[A0: IsJsValue, A1: IsJsValue, A2: IsJsValue, A3: IsJsValue, A4: IsJsValue, A5: IsJsValue, A6: IsJsValue, A7: IsJsValue, A8: IsJsValue](name: A0, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8): JsValue
148
147
  target js sync "return self_[name_].call(self_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_)"
149
148
 
150
- call9[A1: IsJsValue, A2: IsJsValue, A3: IsJsValue, A4: IsJsValue, A5: IsJsValue, A6: IsJsValue, A7: IsJsValue, A8: IsJsValue, A9: IsJsValue](name: String, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8, a9: A9): JsValue
149
+ call9[A0: IsJsValue, A1: IsJsValue, A2: IsJsValue, A3: IsJsValue, A4: IsJsValue, A5: IsJsValue, A6: IsJsValue, A7: IsJsValue, A8: IsJsValue, A9: IsJsValue](name: A0, a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8, a9: A9): JsValue
151
150
  target js sync "return self_[name_].call(self_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_)"
152
151
 
153
152
 
@@ -155,34 +154,34 @@ extend self: JsValue {
155
154
  target js sync "return self_.apply(this_, arguments_)"
156
155
 
157
156
  callValue0(): JsValue
158
- target js sync "return self_.call(self_)"
157
+ target js sync "return self_.call(void 0)"
159
158
 
160
159
  callValue1[A1: IsJsValue](a1: A1): JsValue
161
- target js sync "return self_.call(self_, a1_)"
160
+ target js sync "return self_.call(void 0, a1_)"
162
161
 
163
162
  callValue2[A1: IsJsValue, A2: IsJsValue](a1: A1, a2: A2): JsValue
164
- target js sync "return self_.call(self_, a1_, a2_)"
163
+ target js sync "return self_.call(void 0, a1_, a2_)"
165
164
 
166
165
  callValue3[A1: IsJsValue, A2: IsJsValue, A3: IsJsValue](a1: A1, a2: A2, a3: A3): JsValue
167
- target js sync "return self_.call(self_, a1_, a2_, a3_)"
166
+ target js sync "return self_.call(void 0, a1_, a2_, a3_)"
168
167
 
169
168
  callValue4[A1: IsJsValue, A2: IsJsValue, A3: IsJsValue, A4: IsJsValue](a1: A1, a2: A2, a3: A3, a4: A4): JsValue
170
- target js sync "return self_.call(self_, a1_, a2_, a3_, a4_)"
169
+ target js sync "return self_.call(void 0, a1_, a2_, a3_, a4_)"
171
170
 
172
171
  callValue5[A1: IsJsValue, A2: IsJsValue, A3: IsJsValue, A4: IsJsValue, A5: IsJsValue](a1: A1, a2: A2, a3: A3, a4: A4, a5: A5): JsValue
173
- target js sync "return self_.call(self_, a1_, a2_, a3_, a4_, a5_)"
172
+ target js sync "return self_.call(void 0, a1_, a2_, a3_, a4_, a5_)"
174
173
 
175
174
  callValue6[A1: IsJsValue, A2: IsJsValue, A3: IsJsValue, A4: IsJsValue, A5: IsJsValue, A6: IsJsValue](a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6): JsValue
176
- target js sync "return self_.call(self_, a1_, a2_, a3_, a4_, a5_, a6_)"
175
+ target js sync "return self_.call(void 0, a1_, a2_, a3_, a4_, a5_, a6_)"
177
176
 
178
177
  callValue7[A1: IsJsValue, A2: IsJsValue, A3: IsJsValue, A4: IsJsValue, A5: IsJsValue, A6: IsJsValue, A7: IsJsValue](a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7): JsValue
179
- target js sync "return self_.call(self_, a1_, a2_, a3_, a4_, a5_, a6_, a7_)"
178
+ target js sync "return self_.call(void 0, a1_, a2_, a3_, a4_, a5_, a6_, a7_)"
180
179
 
181
180
  callValue8[A1: IsJsValue, A2: IsJsValue, A3: IsJsValue, A4: IsJsValue, A5: IsJsValue, A6: IsJsValue, A7: IsJsValue, A8: IsJsValue](a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8): JsValue
182
- target js sync "return self_.call(self_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_)"
181
+ target js sync "return self_.call(void 0, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_)"
183
182
 
184
183
  callValue9[A1: IsJsValue, A2: IsJsValue, A3: IsJsValue, A4: IsJsValue, A5: IsJsValue, A6: IsJsValue, A7: IsJsValue, A8: IsJsValue, A9: IsJsValue](a1: A1, a2: A2, a3: A3, a4: A4, a5: A5, a6: A6, a7: A7, a8: A8, a9: A9): JsValue
185
- target js sync "return self_.call(self_, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_)"
184
+ target js sync "return self_.call(void 0, a1_, a2_, a3_, a4_, a5_, a6_, a7_, a8_, a9_)"
186
185
 
187
186
 
188
187
  new(this: JsValue, arguments: List[JsValue]): JsValue