mirror of
https://github.com/enso-org/enso.git
synced 2024-09-11 21:27:15 +03:00
Defining Methods for Operators (#1189)
This commit is contained in:
parent
c824c1cb7b
commit
8e07e0347f
@ -1,2 +0,0 @@
|
||||
type True
|
||||
type False
|
@ -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
|
||||
|
@ -1,3 +0,0 @@
|
||||
type Just a
|
||||
|
||||
Just.isJust = True
|
@ -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.
|
||||
|
||||
|
@ -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 + "."
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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 ////
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user