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

Closes #27. Operator methods #29

Merged
merged 2 commits into from
Nov 6, 2017
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
74 changes: 74 additions & 0 deletions spec/interpreter/nodes/call_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,80 @@ describe "Interpreter - Call" do
Foo.a? + Foo.b!
), [val(3)]

# Operators can be overloaded by defining a method with the operator as the
# name on an object.
# The match operator overload will have special semantics. These are TBD from
# https://github.com/myst-lang/myst/issues/11.
[
"+", "-", "*", "/", "%",
"<", "<=", "!=", "==", ">=", ">"
].each do |op|
it_interprets %Q(
deftype Foo
def a; @a; end
def initialize(a)
@a = a
end

def #{op}(other : Foo)
%Foo{@a + other.a}
end
end

f1 = %Foo{1}
f2 = %Foo{2}
f3 = f1 #{op} f2
f3.a
), [val(3)]

# Operators can also be defined statically to do some type algebra.
it_interprets %Q(
deftype Foo
defstatic #{op}(other : Foo)
:called_op_on_type
end
end

Foo #{op} Foo
), [val(:called_op_on_type)]

# Or on modules, for whatever that might be worth... (I guess this could
# define operators in a module that could be included? Not sure why it
# would be done outside of an `include`, though).
it_interprets %Q(
defmodule Foo
def #{op}(other)
:called_op_on_module
end
end

Foo #{op} Foo
), [val(:called_op_on_module)]
end

# Access and access assignment can also be overloaded.
it_interprets %q(
deftype Foo
def initialize
@values = [1, 2, 3]
end

def [](idx : Integer)
@values[idx]
end

def []=(idx : Integer, value)
@values[idx] = value
end

def values; @values; end
end

f1 = %Foo{}
f1[2] = 5
f1.values
), [val([1, 2, 5])]


# When looking up a function, the current lexical scope should be checked for
# overrides. However, parent lexical scopes should be ignored.
Expand Down
10 changes: 10 additions & 0 deletions spec/syntax/parser_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,16 @@ describe "Parser" do
end
), Def.new("foo", [p("list", l([1, u("_")]), restriction: c("List")), p(nil, l(nil)), p("b", restriction: c("Integer"))])

# Some operators are also allowed as method names for overloading.
[
"+", "-", "*", "/", "%", "[]", "[]=",
"<", "<=", "!=", "==", ">=", ">"
].each do |op|
it_parses %Q(def #{op}; end), Def.new(op)
it_parses %Q(def #{op}(); end), Def.new(op)
it_parses %Q(def #{op}(other); end), Def.new(op, [p("other")])
it_parses %Q(def #{op}(a, b); end), Def.new(op, [p("a"), p("b")])
end


# Module definitions
Expand Down
5 changes: 5 additions & 0 deletions src/myst/interpreter/native_lib/list.cr
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ module Myst
this.elements[index.value]
end

NativeLib.method :list_access_assign, TList, index : TInteger, value : Value do
this.elements[index.value] = value
end


def init_list(root_scope : Scope)
list_type = TType.new("List", root_scope)
Expand All @@ -26,6 +30,7 @@ module Myst
NativeLib.def_instance_method(list_type, :each, :list_each)
NativeLib.def_instance_method(list_type, :+, :list_add)
NativeLib.def_instance_method(list_type, :[], :list_access)
NativeLib.def_instance_method(list_type, :[]=, :list_access_assign)

list_type
end
Expand Down
38 changes: 32 additions & 6 deletions src/myst/syntax/parser.cr
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,7 @@ module Myst
start = expect(Token::Type::DEF, Token::Type::DEFSTATIC)
static = (start.type == Token::Type::DEFSTATIC)
skip_space
name = expect(Token::Type::IDENT).value
# If the name is unmodified, it can be followed by an `=` to create an
# assignment method.
if !modified_ident?(name) && accept(Token::Type::EQUAL)
name += "="
end
name = parse_def_name
method_def = Def.new(name, static: static).at(start.location)
push_var_scope

Expand Down Expand Up @@ -203,6 +198,37 @@ module Myst
end
end

def parse_def_name
case (token = current_token).type
when Token::Type::IDENT
expect(Token::Type::IDENT)
name = token.value
# If the name is unmodified, it can be followed by an `=` to create an
# assignment method.
if !modified_ident?(name) && accept(Token::Type::EQUAL)
name += "="
end
name
when Token::Type::LBRACE
# An access overload is written as `[]`, so an RBRACE must also be
# given for the method name to be valid.
expect(Token::Type::LBRACE)
expect(Token::Type::RBRACE)
# An `=` can also be given to specify access assignment
if accept(Token::Type::EQUAL)
return "[]="
else
return "[]"
end
when .overloadable_operator?
read_token
# Any overloadable operator is also allowed
token.value
else
raise ParseError.new("Invalid name for def: #{token.value}")
end
end

def parse_param(allow_splat=true)
param = Param.new

Expand Down
11 changes: 10 additions & 1 deletion src/myst/syntax/token.cr
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,15 @@ module Myst
end

def self.binary_operators
[ PLUS, MINUS, STAR, SLASH, EQUAL, MATCH, LESS, LESSEQUAL,
[ PLUS, MINUS, STAR, SLASH, MODULO, EQUAL, MATCH, LESS, LESSEQUAL,
GREATEREQUAL, GREATER, NOTEQUAL, EQUALEQUAL, ANDAND, OROR]
end

def self.overloadable_operators
[ PLUS, MINUS, STAR, SLASH, MODULO, MATCH, LESS, LESSEQUAL,
NOTEQUAL, EQUALEQUAL, GREATEREQUAL, GREATER]
end

def unary_operator?
self.class.unary_operators.includes?(self)
end
Expand All @@ -179,6 +184,10 @@ module Myst
self.class.binary_operators.includes?(self)
end

def overloadable_operator?
self.class.overloadable_operators.includes?(self)
end

def operator?
unary_operator? || binary_operator?
end
Expand Down