Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix union over tuples implementation #106

Merged
merged 1 commit into from
May 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions src/main/scala/com/melvic/chi/output/show/Show.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.melvic.chi.output.show

import com.melvic.chi.ast.{Definition, Proposition}
import com.melvic.chi.ast.Proof.{Abstraction, Conjunction, EitherCases, Variable}
import com.melvic.chi.ast.{Definition, Proof, Proposition}
import com.melvic.chi.config.Preferences
import com.melvic.chi.output.show.Show.DefaultCSVSeparator
import com.melvic.chi.output.{DefLayout, ProofLayout, SignatureLayout}
Expand Down Expand Up @@ -78,5 +79,20 @@ object Show {
def toCSV[A](items: List[A], separator: String = DefaultCSVSeparator): String =
items.mkString(separator)

def error: String = "??? /* <error: \"unable to render\"> */"
def error(reason: String = "unable to render"): String =
"??? " + message("error", reason)

def warning(reason: String): String =
message("warning", reason)

def message(label: String, reason: String): String =
s"""/* $label: "$reason" */"""

def useUnionNameInBranch(branch: Abstraction, unionName: Variable): Proof = {
val Abstraction(in, out) = branch
in match {
case Variable(targetName, _) => Proof.rename(out, Variable.fromName(targetName) :: Nil, unionName :: Nil)
case _ => out
}
}
}
8 changes: 4 additions & 4 deletions src/main/scala/com/melvic/chi/output/show/ShowPython.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ class ShowPython(implicit val prefs: Preferences) extends Show with ScalaLike wi
case PConjunction(components) => s"(${bodyCSV(components)})"
case PLeft(proof) => show.proof(proof)
case PRight(proof) => show.proof(proof)
case Match(name, ec @ EitherCases(Abstraction(_: Variable, _), Abstraction(_: Variable, _))) =>
Utils.showMatchUnion(name, ec, show.proof)((lType, leftResult, rightResult) =>
s"$leftResult if type(${show.proof(name)}) is ${show.proposition(lType)} else $rightResult"
)
case Match(name, EitherCases(left @ Abstraction(Variable(_, lType), _), right)) =>
val ifBody = show.proof(Show.useUnionNameInBranch(left, name))
val elseBody = show.proof(Show.useUnionNameInBranch(right, name))
s"$ifBody if type(${show.proof(name)}) is ${show.proposition(lType)} else $elseBody"
case Match(Variable(name, _), function @ Abstraction(_: PConjunction, _)) =>
show.proof(Application.ofUnary(function, Variable.fromName("*" + name)))
case Abstraction(PConjunction(Nil), out) => s"lambda: ${show.proof(out)}"
Expand Down
54 changes: 41 additions & 13 deletions src/main/scala/com/melvic/chi/output/show/ShowTypescript.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package com.melvic.chi.output.show

import com.melvic.chi.ast.Proof.{Conjunction => PConjunction, _}
import com.melvic.chi.ast.Proposition._
import com.melvic.chi.ast.{Proof, Proposition, Signature}
import com.melvic.chi.ast.{Proposition, Signature}
import com.melvic.chi.config.Preferences
import com.melvic.chi.output.{ParamsInParens, ProofLayout, SignatureLayout}
import com.melvic.chi.parsers.Language

class ShowTypescript(implicit val prefs: Preferences)
extends Show
Expand Down Expand Up @@ -34,21 +35,40 @@ class ShowTypescript(implicit val prefs: Preferences)
case PConjunction(components) => "[" + csv(components)(show.proof) + "]"
case PLeft(proof) => show.proof(proof)
case PRight(proof) => show.proof(proof)
case Match(name, ec @ EitherCases(Abstraction(_: Variable, _), Abstraction(_: Variable, _))) =>
Utils.showMatchUnion(name, ec, show.proof) { (lType, leftResult, rightResult) =>
val leftCondition =
nest(s"if (typeof(${show.proof(name)}) === '${show.proposition(lType)}')${line}return $leftResult")
val rightCondition = "else return " + rightResult
val blockContent = leftCondition + line + rightCondition
val block = nest(s"{$line$blockContent") + line + "}"
s"(() => $block)()"
case Match(name, EitherCases(left @ Abstraction(Variable(_, lType), _), right)) =>
val ifBranch = {
val ifBody = show.proof(Show.useUnionNameInBranch(left, name))
val predicate = s"typeof(${show.proof(name)}) === \"${show.proposition(lType)}\""
val condition = lType match {
case Atom(typeName) =>
if (Language.Typescript.builtInTypes.contains(typeName)) predicate
else nest(Show.error(s"Typescript support does not$line include unions of non-builtin types"))
case _ => predicate
}
nest(s"if ($condition)${line}return $ifBody")
}
val elseBranch = "else return " + show.proof(Show.useUnionNameInBranch(right, name))
show.ifElse(ifBranch, elseBranch)
case Match(name, EitherCases(Abstraction(PConjunction(lInComps), lOut), right)) =>
val ifBranch = {
val nameString = show.proof(name)
val destructure = s"const [${csv(lInComps)(show.proof)}] = $nameString"
val condition = {
val warning = nest(
Show.warning("You might need to do" + line + "extra checks for the types of the components")
)
s"if ($nameString instanceof Array $warning)"
}
val ifBody = show.proof(lOut)
nest(condition + " {" + line + destructure + line + "return " + ifBody)
}
val elseBranch = "} else return " + show.proof(Show.useUnionNameInBranch(right, name))
show.ifElse(ifBranch, elseBranch)
case Match(name, Abstraction(PConjunction(components), body)) =>
val destructure = s"const [${csv(components)(show.proof)}] = ${show.proof(name)}"
val comment = "// Note: This is verbose for compatibility reasons"
val blockContent = comment + line + destructure + line + "return " + show.proof(body)
val block = nest(s"{$line$blockContent") + line + "}"
s"(() => $block)()"
show.invokedLambda(blockContent)
case Abstraction(Variable(name, paramType), out) =>
s"($name: ${show.proposition(paramType)}) => ${show.proof(out)}"
case Abstraction(PConjunction(components), out) =>
Expand All @@ -63,7 +83,7 @@ class ShowTypescript(implicit val prefs: Preferences)
case Infix(left, right) =>
s"${show.proof(left)}.${show.proof(right)}"
case Indexed(proof, index) => s"${show.proof(proof)}[${index - 1}]"
case _ => Show.error
case _ => Show.error()
}

def signatureWithSplit(split: Boolean): SignatureLayout = {
Expand All @@ -74,6 +94,14 @@ class ShowTypescript(implicit val prefs: Preferences)
}
val paramsString = paramList(params, split)

s"function $name$typeParamsString$paramsString: ${show.proposition(returnType)} "
s"function $name$typeParamsString$paramsString: ${show.proposition(returnType)}"
}

private def ifElse(ifBranch: String, elseBranch: String): String =
invokedLambda(ifBranch + line + elseBranch)

private def invokedLambda(blockContent: String): String = {
val block = nest(s"{$line$blockContent") + line + "}"
s"(() => $block)()"
}
}
21 changes: 0 additions & 21 deletions src/main/scala/com/melvic/chi/output/show/Utils.scala

This file was deleted.

2 changes: 1 addition & 1 deletion src/main/scala/com/melvic/chi/parsers/Language.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ object Language {
}

case object Scala extends Language {
override val builtInTypes = List("Char", "Byte", "Short", "Int", "Long", "Float", "Double", "String")
override val builtInTypes = List("Char", "Byte", "Short", "Int", "Long", "Float", "Double", "String", "Boolean")
}

case object Haskell extends Language {
Expand Down
19 changes: 5 additions & 14 deletions src/main/scala/com/melvic/chi/parsers/TypescriptParser.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package com.melvic.chi.parsers

import com.melvic.chi.ast.Proof.Variable
import com.melvic.chi.ast.Proposition.{Atom, Conjunction, Disjunction, Identifier, Implication, Labeled}
import com.melvic.chi.ast.Proposition.{Conjunction, Disjunction, Implication, Labeled}
import com.melvic.chi.ast.{Proposition, Signature}

import scala.util.matching.Regex

object TypescriptParser extends LanguageParser with NamedParams {
override val language = Language.Typescript

Expand All @@ -22,22 +20,15 @@ object TypescriptParser extends LanguageParser with NamedParams {
Signature(name, typeParams.getOrElse(Nil), paramsList, proposition)
}

lazy val complex: PackratParser[Proposition] = implication | conjunction | disjunction

override lazy val proposition: PackratParser[Proposition] =
complex | identifier
implication | disjunction | conjunction | identifier

val conjunction: Parser[Conjunction] = "[" ~> repsep(proposition, ",") <~ "]" ^^ { Conjunction }

val disjunction: Parser[Disjunction] = {
// for now, disallow union of non-built-in types
val validComponents = regex(new Regex(Language.Typescript.builtInTypes.mkString("|"))) ^^ { Identifier }

val proposition = complex | validComponents
proposition ~ rep1("|" ~> proposition) ^^ {
case left ~ (right :: rest) => Disjunction.fromList(left, right, rest)
val disjunction: Parser[Disjunction] =
proposition ~ rep1("|" ~> proposition) ^^ { case left ~ (right :: rest) =>
Disjunction.fromList(left, right, rest)
}
}

/**
* The antecedent of a Typescript implication can only be a list of name-type pairs.
Expand Down
81 changes: 66 additions & 15 deletions src/test/scala/com/melvic/chi/tests/TSFunctionsSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class TSFunctionsSpec extends BaseSpec {
"identity" should "map the input to itself" in {
generateAndShowWithInfo("function id<A>(a: A): A") should be(
output(
"""function id<A>(a: A): A {
"""function id<A>(a: A): A {
| return a;
|}""".stripMargin
)
Expand All @@ -19,7 +19,7 @@ class TSFunctionsSpec extends BaseSpec {
"(A => B) => A => B" should "apply the function to the input of the resulting function" in {
generateAndShowWithInfo("function apply<A, B>(f: (a: A) => B, a: A): B") should be(
output(
"""function apply<A, B>(f: (a: A) => B, a: A): B {
"""function apply<A, B>(f: (a: A) => B, a: A): B {
| return f(a);
|}""".stripMargin
)
Expand All @@ -29,7 +29,7 @@ class TSFunctionsSpec extends BaseSpec {
"fst" should "return the first element" in {
generateAndShowWithInfo("function fst<A, B>(pair: [A, B]): A") should be(
output(
"""function fst<A, B>(pair: [A, B]): A {
"""function fst<A, B>(pair: [A, B]): A {
| return (() => {
| // Note: This is verbose for compatibility reasons
| const [a, b] = pair
Expand All @@ -43,7 +43,7 @@ class TSFunctionsSpec extends BaseSpec {
"compose" should "apply the first function after the second" in {
generateAndShowWithInfo("function compose<A, B, C>(f: (b: B) => C, g: (a: A) => B): (a: A) => C") should be(
output(
"""function compose<A, B, C>(f: (b: B) => C, g: (a: A) => B): (a: A) => C {
"""function compose<A, B, C>(f: (b: B) => C, g: (a: A) => B): (a: A) => C {
| return (a: A) => f(g(a));
|}""".stripMargin
)
Expand All @@ -53,7 +53,7 @@ class TSFunctionsSpec extends BaseSpec {
"conjunction" should "depend on the proofs of its components" in {
generateAndShowWithInfo("function foo<A, B>(a: A, f: (b: B) => [A, B], b: B): [A, B]") should be(
output(
"""function foo<A, B>(a: A, f: (b: B) => [A, B], b: B): [A, B] {
"""function foo<A, B>(a: A, f: (b: B) => [A, B], b: B): [A, B] {
| return [a, b];
|}""".stripMargin
)
Expand All @@ -63,7 +63,7 @@ class TSFunctionsSpec extends BaseSpec {
"union" should "default to left when the evaluation succeeds" in {
generateAndShowWithInfo("function left(str: string): string | string") should be(
output(
"""function left(str: string): string | string {
"""function left(str: string): string | string {
| return str;
|}""".stripMargin
)
Expand All @@ -73,7 +73,7 @@ class TSFunctionsSpec extends BaseSpec {
"all assumptions" should "be considered" in {
generateAndShowWithInfo("\nfunction foo<A, B, C>(f: (a: A) => C, g: (b: B) => C): (b: B) => C") should be(
output(
"""function foo<A, B, C>(f: (a: A) => C, g: (b: B) => C): (b: B) => C {
"""function foo<A, B, C>(f: (a: A) => C, g: (b: B) => C): (b: B) => C {
| return (b: B) => g(b);
|}""".stripMargin
)
Expand All @@ -83,7 +83,7 @@ class TSFunctionsSpec extends BaseSpec {
"implication" should "evaluate its antecedent recursively" in {
generateAndShowWithInfo("function foo<A, B, C>(f: (a: A) => B, g: (h: (a: A) => B) => C): C") should be(
output(
"""function foo<A, B, C>(f: (a: A) => B, g: (h: (a: A) => B) => C): C {
"""function foo<A, B, C>(f: (a: A) => B, g: (h: (a: A) => B) => C): C {
| return g((a: A) => f(a));
|}""".stripMargin
)
Expand All @@ -101,9 +101,9 @@ class TSFunctionsSpec extends BaseSpec {
"""function foo(
| f: (s: string) => boolean,
| g: (n: number) => boolean
|): (either: string | number) => boolean {
|): (either: string | number) => boolean {
| return (e: string | number) => (() => {
| if (typeof(e) === 'string')
| if (typeof(e) === "string")
| return f(e)
| else return g(e)
| })();
Expand All @@ -125,12 +125,12 @@ class TSFunctionsSpec extends BaseSpec {
| f: (s: string) => boolean,
| g: (n: number) => boolean,
| h: (b: boolean) => boolean
|): boolean {
|): boolean {
| return (() => {
| if (typeof(u) === 'string')
| if (typeof(u) === "string")
| return f(u)
| else return (() => {
| if (typeof(u) === 'number')
| if (typeof(u) === "number")
| return g(u)
| else return u
| })()
Expand All @@ -143,18 +143,69 @@ class TSFunctionsSpec extends BaseSpec {
"component of a product consequent" should "be accessible if the function is applied" in {
generateAndShowWithInfo("function foo<A, B, C>(f: (a: A) => [C, B], a: A): B") should be(
output(
"""function foo<A, B, C>(f: (a: A) => [C, B], a: A): B {
"""function foo<A, B, C>(f: (a: A) => [C, B], a: A): B {
| return f(a)[1];
|}""".stripMargin
)
)

generateAndShowWithInfo("function foo<A, B>(f: (a: A) => [A, B]): [(a: A) => A, (a: A) => B]") should be(
output(
"""function foo<A, B>(f: (a: A) => [A, B]): [(a: A) => A, (a: A) => B] {
"""function foo<A, B>(f: (a: A) => [A, B]): [(a: A) => A, (a: A) => B] {
| return [(a: A) => a, (a: A) => f(a)[1]];
|}""".stripMargin
)
)
}

"checking the type of type-param" should "render an error message" in {
generateAndShowWithInfo(
"""function foo<A>(
| u: A | string,
| f: (s: string) => boolean,
| g: (n: A) => boolean
|): boolean""".stripMargin
) should be(
output(
"""function foo<A>(
| u: A | string,
| f: (s: string) => boolean,
| g: (n: A) => boolean
|): boolean {
| return (() => {
| if (??? /* error: "Typescript support does not
| include unions of non-builtin types" */)
| return g(u)
| else return f(u)
| })();
|}""".stripMargin
)
)
}

"union over tuples" should "render the correct if-else branch" in {
generateAndShowWithInfo(
"""function foo(
| u: [string, number] | number,
| f: (s: [string, number]) => boolean,
| g: (n: number) => boolean
|): boolean""".stripMargin
) should be(
output(
"""function foo(
| u: [string, number] | number,
| f: (s: [string, number]) => boolean,
| g: (n: number) => boolean
|): boolean {
| return g((() => {
| if (u instanceof Array /* warning: "You might need to do
| extra checks for the types of the components" */) {
| const [s, n] = u
| return n
| } else return u
| })());
|}""".stripMargin
)
)
}
}