From 9bed44089c34b4af31f371da222a25273213d4d6 Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Wed, 7 Dec 2022 10:30:26 +0800 Subject: [PATCH 1/3] Add access flags parsing --- shared/src/main/scala/mlscript/NewLexer.scala | 5 +++- .../src/main/scala/mlscript/NewParser.scala | 23 +++++++++++++++---- shared/src/main/scala/mlscript/syntax.scala | 5 ++++ shared/src/test/diff/parser/Classes.mls | 17 ++++++++++++++ 4 files changed, 44 insertions(+), 6 deletions(-) diff --git a/shared/src/main/scala/mlscript/NewLexer.scala b/shared/src/main/scala/mlscript/NewLexer.scala index d1d35619e7..ef385cfebe 100644 --- a/shared/src/main/scala/mlscript/NewLexer.scala +++ b/shared/src/main/scala/mlscript/NewLexer.scala @@ -251,7 +251,10 @@ object NewLexer { "interface", "new", "namespace", - "type" + "type", + "public", + "private", + "protected" ) def printToken(tl: TokLoc): Str = tl match { diff --git a/shared/src/main/scala/mlscript/NewParser.scala b/shared/src/main/scala/mlscript/NewParser.scala index 8ca39a57ef..14c94703ff 100644 --- a/shared/src/main/scala/mlscript/NewParser.scala +++ b/shared/src/main/scala/mlscript/NewParser.scala @@ -291,13 +291,26 @@ abstract class NewParser(origin: Origin, tokens: Ls[Stroken -> Loc], raiseFun: D R(NuTypeDef(kind, tn, tparams, params, ps, body)) // TODO make `fun` by-name and `let` by-value - case (KEYWORD(kwStr @ ("fun" | "let")), l0) :: c => // TODO support rec? + case (KEYWORD(kwStr @ ("public" | "private" | "protected" | "fun" | "let")), l0) :: c => // TODO support rec? consume + // TODO: apply access flags in OOP. + val flag = if (kwStr === "private") PrivateFlag + else if (kwStr === "protected") ProtectedFlag + else PublicFlag // if there is no access flag keyword, we consider it as public + + val (decKw, wrongKw) = if (kwStr === "fun" || kwStr === "let") (kwStr, false) else yeetSpaces match { + case (KEYWORD(decStr @ ("fun" | "let")), _) :: _ => consume; System.out.println(s"fuck: ${kwStr}, ${decStr}"); (decStr, false) + case _ => + val (tkstr, loc) = c.headOption.fold(("end of input", lastLoc))(_.mapFirst(_.describe).mapSecond(some)) + err(msg"Expected keyword let or fun" -> loc :: Nil) + ("", true) + } + val isLetRec = yeetSpaces match { - case (KEYWORD("rec"), l1) :: _ if kwStr === "let" => + case (KEYWORD("rec"), l1) :: _ if decKw === "let" => consume S(true) - case c => if (kwStr === "fun") N else S(false) + case c => if (decKw === "fun") N else S(false) } val (v, success) = yeetSpaces match { case (IDENT(idStr, false), l1) :: _ => @@ -312,8 +325,8 @@ abstract class NewParser(origin: Origin, tokens: Ls[Stroken -> Loc], raiseFun: D // R(errExpr) (Var("").withLoc(curLoc.map(_.left)), false) } - foundErr || !success pipe { implicit fe => - val tparams = if (kwStr === "let") Ls[TypeName]() else yeetSpaces match { + foundErr || wrongKw || !success pipe { implicit fe => + val tparams = if (decKw === "let") Ls[TypeName]() else yeetSpaces match { case (br @ BRACKETS(Angle, toks), loc) :: _ => consume val ts = rec(toks, S(br.innerLoc), br.describe).concludeWith(_.argsMaybeIndented()).map { diff --git a/shared/src/main/scala/mlscript/syntax.scala b/shared/src/main/scala/mlscript/syntax.scala index 06facf4035..f6ee11ff2d 100644 --- a/shared/src/main/scala/mlscript/syntax.scala +++ b/shared/src/main/scala/mlscript/syntax.scala @@ -151,6 +151,11 @@ final case class TypeVar(val identifier: Int \/ Str, nameHint: Opt[Str]) extends final case class PolyType(targs: Ls[TypeName], body: Type) extends PolyTypeImpl +// Access flags in OOP. +sealed trait AccessFlag +final case object PublicFlag extends AccessFlag +final case object PrivateFlag extends AccessFlag +final case object ProtectedFlag extends AccessFlag // New Definitions AST diff --git a/shared/src/test/diff/parser/Classes.mls b/shared/src/test/diff/parser/Classes.mls index 93a015555f..cf33614ae2 100644 --- a/shared/src/test/diff/parser/Classes.mls +++ b/shared/src/test/diff/parser/Classes.mls @@ -119,4 +119,21 @@ class Foo(x, y): Bar(y, x), Baz(x + y) //│ |#class| |Foo|(|x|,| |y|)|#:| |Bar|(|y|,| |x|)|,| |Baz|(|x| |+| |y|)| //│ Parsed: {class Foo(x, y,): Bar (y, x,), Baz (+ (x,) (y,),) {}} +class Foo() { + public let a = 42 + private fun b = 42 + protected let c = 42 +} +//│ |#class| |Foo|(||)| |{|→|#public| |#let| |a| |#=| |42|↵|#private| |#fun| |b| |#=| |42|↵|#protected| |#let| |c| |#=| |42|←|↵|}| +//│ Parsed: {class Foo() {let a = 42; fun b = 42; let c = 42}} + + +class Bar() { + private a = 0 +} +//│ |#class| |Bar|(||)| |{|→|#private| |a| |#=| |0|←|↵|}| +//│ ╔══[PARSE ERROR] Expected keyword let or fun +//│ ║ l.132: private a = 0 +//│ ╙── ^ +//│ Parsed: {class Bar() {let a = 0}} From 5ce42b1c16e3ded078d1b99c923bb05ecf560c2e Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Wed, 7 Dec 2022 10:56:08 +0800 Subject: [PATCH 2/3] Add private/protected export --- .../main/scala/ts2mls/types/Converter.scala | 23 ++++++++----------- .../src/main/scala/ts2mls/types/TSType.scala | 12 +++++++--- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/ts2mls/js/src/main/scala/ts2mls/types/Converter.scala b/ts2mls/js/src/main/scala/ts2mls/types/Converter.scala index 32760642b2..9862aee11d 100644 --- a/ts2mls/js/src/main/scala/ts2mls/types/Converter.scala +++ b/ts2mls/js/src/main/scala/ts2mls/types/Converter.scala @@ -18,14 +18,14 @@ object Converter { "false" -> "false" ) - def generateFunDeclaration(tsType: TSType, name: String)(implicit indent: String = ""): String = tsType match { + def generateFunDeclaration(tsType: TSType, name: String, modifier: String = "")(implicit indent: String = ""): String = tsType match { case TSFunctionType(params, res, typeVars) => { val pList = if (params.isEmpty) "" else params.map(p => s"${convert(p)("")}").reduceLeft((r, p) => s"$r, $p") val tpList = if (typeVars.isEmpty) "" else s"<${typeVars.map(p => convert(p)("")).reduceLeft((r, p) => s"$r, $p")}>" - s"${indent}fun $name$tpList($pList): ${convert(res)("")}" + s"${indent}${modifier}fun $name$tpList($pList): ${convert(res)("")}" } case overload @ TSIgnoredOverload(base, _) => s"${generateFunDeclaration(base, name)} ${overload.warning}" - case inter: TSIntersectionType => s"${indent}fun ${name}: ${Converter.convert(inter)}" + case inter: TSIntersectionType => s"${indent}${modifier}fun ${name}: ${Converter.convert(inter)}" case _ => throw new AssertionError("non-function type is not allowed.") } @@ -58,16 +58,13 @@ object Converter { private def convertRecord(typeName: String, members: Map[String, TSMemberType], typeVars: List[TSTypeParameter], parents: List[TSType], statics: Map[String, TSMemberType], constructorList: List[TSType])(implicit indent: String) = { - val allRecs = members.toList.map((m) => m._2.modifier match { - case Public => - if (typeName === "trait ") s"${m._1}: ${convert(m._2)}," - else m._2.base match { - case _: TSFunctionType => s"${generateFunDeclaration(m._2.base, m._1)(indent + " ")}\n" - case _: TSIgnoredOverload => s"${generateFunDeclaration(m._2.base, m._1)(indent + " ")}\n" - case _ => s"${indent} let ${m._1}: ${convert(m._2)}\n" - } - case _ => "" // TODO: deal with private/protected members - }) ::: + val allRecs = members.toList.map((m) => + if (typeName === "trait ") s"${m._1}: ${convert(m._2)}," + else m._2.base match { + case _: TSFunctionType => s"${generateFunDeclaration(m._2.base, m._1, m._2.modifier.toString())(indent + " ")}\n" + case _: TSIgnoredOverload => s"${generateFunDeclaration(m._2.base, m._1, m._2.modifier.toString())(indent + " ")}\n" + case _ => s"${indent} ${m._2.modifier}let ${m._1}: ${convert(m._2)}\n" + }) ::: statics.toList.map((s) => s._2.modifier match { case Public => s._2.base match { case _: TSClassType => convert(s._2)(indent + " ") + "\n" diff --git a/ts2mls/js/src/main/scala/ts2mls/types/TSType.scala b/ts2mls/js/src/main/scala/ts2mls/types/TSType.scala index ce0527db8f..d44cb968e5 100644 --- a/ts2mls/js/src/main/scala/ts2mls/types/TSType.scala +++ b/ts2mls/js/src/main/scala/ts2mls/types/TSType.scala @@ -1,9 +1,15 @@ package ts2mls.types sealed abstract class TSAccessModifier -case object Public extends TSAccessModifier -case object Private extends TSAccessModifier -case object Protected extends TSAccessModifier +case object Public extends TSAccessModifier { + override def toString() = "" +} +case object Private extends TSAccessModifier { + override def toString() = "private " // add a space after the keyword so we can generate `private let...` +} +case object Protected extends TSAccessModifier { + override def toString() = "protected " +} sealed abstract class TSType case class TSParameterType(name: String, val tp: TSType) extends TSType // record both parameter's name and parameter's type From 8c8863102ba45f22ac1c934a0c7fd754729927da Mon Sep 17 00:00:00 2001 From: NeilKleistGao Date: Mon, 12 Dec 2022 12:59:20 +0800 Subject: [PATCH 3/3] Remove test output --- shared/src/main/scala/mlscript/NewParser.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/main/scala/mlscript/NewParser.scala b/shared/src/main/scala/mlscript/NewParser.scala index 14c94703ff..6b5b70834b 100644 --- a/shared/src/main/scala/mlscript/NewParser.scala +++ b/shared/src/main/scala/mlscript/NewParser.scala @@ -299,7 +299,7 @@ abstract class NewParser(origin: Origin, tokens: Ls[Stroken -> Loc], raiseFun: D else PublicFlag // if there is no access flag keyword, we consider it as public val (decKw, wrongKw) = if (kwStr === "fun" || kwStr === "let") (kwStr, false) else yeetSpaces match { - case (KEYWORD(decStr @ ("fun" | "let")), _) :: _ => consume; System.out.println(s"fuck: ${kwStr}, ${decStr}"); (decStr, false) + case (KEYWORD(decStr @ ("fun" | "let")), _) :: _ => consume; (decStr, false) case _ => val (tkstr, loc) = c.headOption.fold(("end of input", lastLoc))(_.mapFirst(_.describe).mapSecond(some)) err(msg"Expected keyword let or fun" -> loc :: Nil)