Defining Methods for Operators (#1189)

This commit is contained in:
Marcin Kostrzewa 2020-10-05 11:32:32 +02:00 committed by GitHub
parent c824c1cb7b
commit 8e07e0347f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 164 additions and 79 deletions

View File

@ -1,2 +0,0 @@
type True
type False

View File

@ -60,8 +60,3 @@ type Math
to its diameter.
Math.pi : Decimal
Math.pi = 3.141592653589793
## Equality definition for types defining `==`.
TODO remove when operators can be defined in-language.
Any.equals that = this == that

View File

@ -1,3 +0,0 @@
type Just a
Just.isJust = True

View File

@ -11,7 +11,9 @@ type Os
Create an Os object from text.
from_text: Text -> Os
from_text os =
if os.equals "linux" then Linux else if os.equals "macos" then MacOS else if os.equals "windows" then Windows else Unknown
if os == "linux" then Linux else
if os == "macos" then MacOS else
if os == "windows" then Windows else Unknown
## Return the type of operating system.

View File

@ -33,7 +33,7 @@ type Assertion
fail message = Panic.throw (Failure message)
## Asserts that `this` value is equal to the expected value.
Any.should_equal that = case this.equals that of
Any.should_equal that = case this == that of
True -> Success
False ->
msg = this.to_text + " did not equal " + that.to_text + "."

View File

@ -66,9 +66,9 @@ Text.split_at separator =
The string 'é' (i.e. the character U+00E9, LATIN SMALL LETTER E WITH
ACUTE) is canonically the same as the string 'e\u0301' (i.e. the letter
`e` followed by U+0301, COMBINING ACUTE ACCENT). Therefore:
('é'.equals 'e\u0301') == True
Text.equals : Text -> Boolean
Text.equals that = Text_Utils.equals [this, that]
('é' == 'e\u0301') == True
Text.== : Text -> Boolean
Text.== that = Text_Utils.equals [this, that]
## Returns a vector containing bytes representing the UTF-8 encoding of the
input text.

View File

@ -41,7 +41,7 @@ type Vector
builder = Vector.new_builder
do_read =
item = IO.readln
if item.equals "end" then Unit else
if item == "end" then Unit else
builder.append item
do_read
do_read
@ -141,14 +141,26 @@ type Vector
## Checks whether this vector is equal to `that`. Two vectors are considered
equal, when they have the same length and their items are pairwise equal.
equals : Vector -> Boolean
equals that =
== : Vector -> Boolean
== that =
arr1 = this.to_array
arr2 = that.to_array
eq_at i = (arr1.at i) . equals (arr2.at i)
eq_at i = arr1.at i == arr2.at i
r = if arr1.length == arr2.length then 0.upto arr1.length . every eq_at else False
r
## Concatenates two vectors, resulting in a new vector, containing all the
elements of `this`, followed by all the elements of `that`.
+ : Vector -> Vector
+ that =
this_len = this.length
arr = Array.new (this_len + that.length)
0.upto this_len . each i->
arr.set_at i (this.at i)
this.length.upto arr.length . each i->
arr.set_at i (that.at i-this_len)
Vector arr
## A builder type for Enso vectors.
A vector builder is a mutable data structure, that allows to gather a
@ -165,7 +177,7 @@ type Vector
builder = Vector.new_builder
do_read =
item = IO.readln
if item.equals "end" then Unit else
if item == "end" then Unit else
builder.append item
do_read
do_read

View File

@ -160,7 +160,10 @@ object AstToIr {
Error.Syntax(inputAst, Error.Syntax.InvalidTypeDefinition)
}
case AstView.MethodDefinition(targetPath, name, args, definition) =>
val nameStr = name match { case AST.Ident.Var.any(name) => name }
val nameId: AST.Ident = name match {
case AST.Ident.Var.any(name) => name
case AST.Ident.Opr.any(opr) => opr
}
val methodRef = if (targetPath.nonEmpty) {
val pathSegments = targetPath.collect {
@ -168,7 +171,7 @@ object AstToIr {
}
val pathNames = pathSegments.map(buildName)
val methodSegments = pathNames :+ buildName(nameStr)
val methodSegments = pathNames :+ buildName(nameId)
val typeSegments = methodSegments.init
@ -182,7 +185,7 @@ object AstToIr {
)
} else {
val typeName = Name.Here(None)
val methodName = buildName(nameStr)
val methodName = buildName(nameId)
Name.MethodReference(
typeName,
methodName,
@ -277,6 +280,13 @@ object AstToIr {
case atom @ AstView.Atom(_, _) => translateModuleSymbol(atom)
case fs @ AstView.FunctionSugar(_, _, _) => translateExpression(fs)
case AST.Comment.any(inputAST) => translateComment(inputAST)
case AstView.Binding(AST.App.Section.Right(opr, arg), body) =>
Function.Binding(
buildName(opr),
List(translateArgumentDefinition(arg)),
translateExpression(body),
getIdentifiedLocation(inputAst)
)
case AstView.TypeAscription(typed, sig) =>
IR.Type.Ascription(
translateExpression(typed),

View File

@ -550,6 +550,8 @@ object AstView {
Some((targetPath, name, List(), rhs))
case MethodBindingLHS(path, methodName, args) =>
Some((path, methodName, args, rhs))
case AST.App.Section.Right(opr, arg) =>
Some((List(), opr, List(arg), rhs))
case AST.Ident.Var.any(name) => Some((List(), name, List(), rhs))
case _ =>
None

View File

@ -360,6 +360,28 @@ class AstToIrTest extends CompilerTest with Inside {
ir.bindings.head shouldBe an[IR.Module.Scope.Definition.Method.Binding]
}
"work for method definitions with operator names" in {
val bindings = """
|My.== : My -> Boolean
|My.== that = this.a == that.a
|""".stripMargin.toIrModule.bindings
val tpIr = bindings(0)
tpIr shouldBe a[IR.Type.Ascription]
val tp = tpIr.asInstanceOf[IR.Type.Ascription]
tp.typed shouldBe a[IR.Name.MethodReference]
val methodRef = tp.typed.asInstanceOf[IR.Name.MethodReference]
methodRef.typePointer.name shouldEqual "My"
methodRef.methodName.name shouldEqual "=="
val methodIr = bindings(1)
methodIr shouldBe a[IR.Module.Scope.Definition.Method.Binding]
val method =
methodIr.asInstanceOf[IR.Module.Scope.Definition.Method.Binding]
method.methodReference.methodName.name shouldEqual "=="
method.methodReference.typePointer.name shouldEqual "My"
}
"not recognise pattern match bindings" in {
val ir =
"""
@ -515,6 +537,24 @@ class AstToIrTest extends CompilerTest with Inside {
ir.asInstanceOf[IR.Error.Syntax]
.reason shouldBe an[IR.Error.Syntax.InterfaceDefinition.type]
}
"allow defining methods with operator names" in {
val body =
"""
|type My
| type My a
|
| + : My -> My
| + that = My this.a+that.a
|""".stripMargin.toIrModule.bindings.head
.asInstanceOf[IR.Module.Scope.Definition.Type]
.body
body(1) shouldBe an[IR.Type.Ascription]
body(2) shouldBe an[IR.Function.Binding]
val fun = body(2).asInstanceOf[IR.Function.Binding]
fun.name.name shouldEqual "+"
}
}
"AST translation for documentation comments" should {

View File

@ -88,6 +88,14 @@ case class ParserDef() extends flexer.Parser[AST.Module] {
}
current.map(go)
}
def takeLast(): Option[AST] = {
current.map(c => {
val app = c.asInstanceOf[AST.App.Prefix]
current = Some(app.func)
app.arg
})
}
}
////////////////
@ -227,27 +235,39 @@ case class ParserDef() extends flexer.Parser[AST.Module] {
ident.current = Some(opr)
}
val char: Pattern = anyOf(";!$%&*+-/<>?^~|:\\")
val errChar: Pattern = char | "=" | "," | "."
val errSfx: Pattern = errChar.many1
val body: Pattern = char.many1
val opsEq: Pattern = "=" | "==" | ">=" | "<=" | "/=" | "#="
val opsDot: Pattern = "." | ".." | "..." | ","
val opsGrp: Pattern = anyOf("()[]{}")
val opsCmm: Pattern = "#" | "##"
val opsNoMod: Pattern = opsEq | opsDot | opsCmm
def onDottedOpr(): Unit = {
logger.trace {
ident.current = Some(AST.Ident.Opr("."))
ident.submit()
ident.current = Some(AST.Ident.Var(currentMatch.substring(1)))
ident.submit()
}
}
val char: Pattern = anyOf(";!$%&*+-/<>?^~|:\\")
val errChar: Pattern = char | "=" | "," | "."
val errSfx: Pattern = errChar.many1
val body: Pattern = char.many1
val opsEq: Pattern = "=" | "==" | ">=" | "<=" | "/=" | "#=" | "!="
val dot: Pattern = "."
val dottedOps: Pattern = dot >> (body | opsEq)
val opsDot: Pattern = dot | ".." | "..." | ","
val opsGrp: Pattern = anyOf("()[]{}")
val opsCmm: Pattern = "#" | "##"
val opsNoMod: Pattern = opsEq | opsDot | opsCmm
val SFX_CHECK = state.define("Operator Suffix Check")
val MOD_CHECK = state.define("Operator Modifier Check")
MOD_CHECK.parent = SFX_CHECK
}
ROOT || opr.body || opr.on(AST.Opr(_))
ROOT || opr.opsNoMod || opr.onNoMod(AST.Opr(_))
ROOT || opr.opsGrp || opr.onGrp(AST.Opr(_))
opr.MOD_CHECK || "=" || opr.onMod()
opr.SFX_CHECK || opr.errSfx || ident.onErrSfx()
opr.SFX_CHECK || always || ident.onNoErrSfx()
ROOT || opr.body || opr.on(AST.Opr(_))
ROOT || opr.dottedOps || opr.onDottedOpr()
ROOT || opr.opsNoMod || opr.onNoMod(AST.Opr(_))
ROOT || opr.opsGrp || opr.onGrp(AST.Opr(_))
opr.MOD_CHECK || "=" || opr.onMod()
opr.SFX_CHECK || opr.errSfx || ident.onErrSfx()
opr.SFX_CHECK || always || ident.onNoErrSfx()
////////////////
//// NUMBER ////

View File

@ -70,7 +70,7 @@ class ParserTest extends AnyFlatSpec with Matchers {
def ?=(out: AST) = testBase in { assertExpr(input, out) }
def ??=(out: Module) = testBase in { assertModule(input, out) }
def testIdentity() = testBase in { assertIdentity(input) }
def testIdentity() = testBase in { assertIdentity(input) }
}
//////////////////////////////////////////////////////////////////////////////
@ -95,22 +95,26 @@ class ParserTest extends AnyFlatSpec with Matchers {
import App.Section._
import App.{Section => Sect}
"++" ?= Sides("++")
"==" ?= Sides("==")
":" ?= Sides(":")
"," ?= Sides(",")
"." ?= Sides(".")
".." ?= Sides("..")
"..." ?= Sides("...")
">=" ?= Sides(">=")
"<=" ?= Sides("<=")
"/=" ?= Sides("/=")
"+=" ?= Mod("+")
"-=" ?= Mod("-")
"===" ?= Ident.InvalidSuffix("==", "=")
"...." ?= Ident.InvalidSuffix("...", ".")
">==" ?= Ident.InvalidSuffix(">=", "=")
"+==" ?= Ident.InvalidSuffix("+", "==")
"++" ?= Sides("++")
"==" ?= Sides("==")
":" ?= Sides(":")
"," ?= Sides(",")
"." ?= Sides(".")
".." ?= Sides("..")
"..." ?= Sides("...")
">=" ?= Sides(">=")
"<=" ?= Sides("<=")
"/=" ?= Sides("/=")
".+" ?= Sect.Right(".", 0, Var("+"))
".<*>" ?= Sect.Right(".", 0, Var("<*>"))
".==" ?= Sect.Right(".", 0, Var("=="))
"Foo.+" ?= App.Infix(Cons("Foo"), 0, Opr("."), 0, Var("+"))
"+=" ?= Mod("+")
"-=" ?= Mod("-")
"===" ?= Ident.InvalidSuffix("==", "=")
"...." ?= Ident.InvalidSuffix("...", ".")
">==" ?= Ident.InvalidSuffix(">=", "=")
"+==" ?= Ident.InvalidSuffix("+", "==")
//////////////////////////////////////////////////////////////////////////////
//// Precedence + Associativity //////////////////////////////////////////////
@ -215,11 +219,11 @@ class ParserTest extends AnyFlatSpec with Matchers {
"\"\"\" \n\n X\n\n Y" ?= Text.Raw(1, 0, line(" X", 0), line(" Y", 0))
"a \"\"\"\n\n\n X\n\n Y" ?= "a" $_ Text.Raw(
0,
1,
line("X", 0, 0),
line("Y", 0)
)
0,
1,
line("X", 0, 0),
line("Y", 0)
)
//// Escapes ////
@ -228,7 +232,7 @@ class ParserTest extends AnyFlatSpec with Matchers {
def escape(code: Text.Segment.RawEscape) = Shape.SegmentRawEscape[AST](code)
Text.Segment.Escape.Character.codes.foreach(i => s"'\\$i'" ?= Text(escape(i)))
Text.Segment.Escape.Control.codes.foreach(i => s"'\\$i'" ?= Text(escape(i)))
Text.Segment.Escape.Control.codes.foreach(i => s"'\\$i'" ?= Text(escape(i)))
"'\\\\'" ?= Text(escape(Esc.Slash))
"'\\''" ?= Text(escape(Esc.Quote))
@ -253,14 +257,16 @@ class ParserTest extends AnyFlatSpec with Matchers {
}
"say \n '''\n Hello\n `World`\npal" ??= Module(
OptLine("say" $_ Block(2, Text(0, 2, line("Hello"), line(expr("World"))))),
OptLine("pal")
)
OptLine(
"say" $_ Block(2, Text(0, 2, line("Hello"), line(expr("World"))))
),
OptLine("pal")
)
"say '''\n Hello\n `World`\npal" ??= Module(
OptLine("say" $_ Text(0, 2, line("Hello"), line(expr("World")))),
OptLine("pal")
)
OptLine("say" $_ Text(0, 2, line("Hello"), line(expr("World")))),
OptLine("pal")
)
//// // // Comments
////// expr("#" , Comment)
@ -309,13 +315,13 @@ class ParserTest extends AnyFlatSpec with Matchers {
def amb(head: AST, lst: List[List[AST]]): Macro.Ambiguous =
Macro.Ambiguous(
Shifted.List1(Macro.Ambiguous.Segment(head)),
Tree(lst.map(_ -> (())): _*)
Tree(lst.map(_ -> ()): _*)
)
def amb(head: AST, lst: List[List[AST]], body: SAST): Macro.Ambiguous =
Macro.Ambiguous(
Shifted.List1(Macro.Ambiguous.Segment(head, Some(body))),
Tree(lst.map(_ -> (())): _*)
Tree(lst.map(_ -> ()): _*)
)
def _amb_group_(i: Int)(t: AST): Macro.Ambiguous =
@ -368,13 +374,13 @@ class ParserTest extends AnyFlatSpec with Matchers {
// )
//
"if a then b" ?= Mixfix(
List1[AST.Ident]("if", "then"),
List1[AST]("a", "b")
)
List1[AST.Ident]("if", "then"),
List1[AST]("a", "b")
)
"if a then b else c" ?= Mixfix(
List1[AST.Ident]("if", "then", "else"),
List1[AST]("a", "b", "c")
)
List1[AST.Ident]("if", "then", "else"),
List1[AST]("a", "b", "c")
)
"if a" ?= amb_if_("a": AST)
"(if a) b" ?= Group(amb_if_("a": AST)) $_ "b"

View File

@ -15,7 +15,7 @@ spec = describe "Text" <|
accent_1 = '\u00E9'
accent_2 = '\u0065\u{301}'
it "should compare strings using utf normalization" <|
"abc".equals "def" . should_be_false
"abc"=="def" . should_be_false
accent_1 . should_equal accent_2
it "should split the text into grapheme clusters" <|
str = kshi + facepalm + accent_1 + accent_2

View File

@ -20,7 +20,10 @@ spec = describe "Vectors" <|
vec.to_text.should_equal "[1, 2, 3, 4]"
mapped.to_text.should_equal "[1, 4, 9, 16]"
it "should define equality" <|
[1,2,3].equals [1,2] . should_be_false
[1,2,3].equals [1,2,3] . should_be_true
[1,2,3].equals [3,4,5] . should_be_false
[1,2,3]==[1,2] . should_be_false
[1,2,3]==[1,2,3] . should_be_true
[1,2,3]==[3,4,5] . should_be_false
it "should define concatenation" <|
concat = [1, 2, 3] + [4, 5, 6]
concat.should_equal [1, 2, 3, 4, 5, 6]