Yuescript icon indicating copy to clipboard operation
Yuescript copied to clipboard

Metamethod operator

Open cursos-vc opened this issue 4 years ago • 23 comments

Hi!

I have a feature suggestion for Yuescript:

# metamethod operator

a = close: true, close#: => print "out of scope"
b = add#: (left, right) -> right - left
c = key1: true, #:add, key2: true

d, e = a.close, a#.close
f = a#\close(1)

Compiles to

local a = setmetatable({close = true}, {__close = function(self) return print("out of scope") end})
local b = setmetatable({}, {__add = function(left, right) return right - left})
local c = setmetatable({key1 = true, key2 = true}, {__add = add})

local d, e = a.close,  getmetatable(a).__close
local f =  getmetatable(a).__close(a, 1)

thanks!

cursos-vc avatar Mar 01 '21 12:03 cursos-vc

How about a little modification for the syntax to make it look more consist?

a = close: true, close#: => print "out of scope"
b = add#: (left, right)-> right - left
c = key1: true, :add#, key2: true

d, e = a.close, a.close#
{:close#, close#: closeA} = a

f = a\close# 1
a.add# = (x, y)-> x + y

close _ = close#: -> print "out of scope"

Compiles to:

local a = setmetatable({
  close = true
}, {
  __close = function(self)
    return print("out of scope")
  end
})
local b = setmetatable({ }, {
  __add = function(left, right)
    return right - left
  end
})
local c = setmetatable({
  key1 = true,
  key2 = true
}, {
  __add = add
})
local d, e = a.close, getmetatable(a).__close
local close, closeA
do
  local _mt_0 = getmetatable(a)
  close, closeA = _mt_0.__close, _mt_0. __close
end
local f =  getmetatable(a).__close(a, 1)
getmetatable(a).__add = function(x, y)
  return x + y
end
local _ <close> = setmetatable({ }, {
  __close = function(self)
    return print("out of scope")
  end
})

pigpigyyy avatar Mar 02 '21 02:03 pigpigyyy

its implementation makes it more consistent. I liked! It got easier. The operator at the end means it is related to the metamethod. +1

cursos-vc avatar Mar 02 '21 03:03 cursos-vc

The new syntax is now implemented in the commit 1df786307c1983b8ce693e3916081a8bcd4e95ae with some extra usages as:

a = close: true, close#: => print "out of scope"
b = add#: (left, right)-> right - left
c = key1: true, :add#, key2: true

d, e = a.close, a.close#
{:close#, close#: closeA} = a

f = a\close# 1
a.add# = (x, y)-> x + y

close _ = close#: -> print "out of scope"

-- shortcut for setting and getting the meta table
a.# = __index: tb
mt = a.#

Compiles to:

local a = setmetatable({
  close = true,
}, {
  __close = function(self)
    return print("out of scope")
  end
})
local b = setmetatable({ }, {
  __add = function(left, right)
    return right - left
  end
})
local c = setmetatable({
  key1 = true,
  key2 = true
}, {
  __add = add
})
local d, e = a.close, getmetatable(a).__close
local close, closeA
do
  local _obj_0 = getmetatable(a)
  close, closeA = _obj_0.__close, _obj_0.__close
end
local f = getmetatable(a):__close(1)
getmetatable(a).__add = function(x, y)
  return x + y
end
local _var_0 = setmetatable({ }, {
  __close = function()
    return print("out of scope")
  end
})
local _ <close> = _var_0
setmetatable(a, {
  __index = tb
})
local mt = getmetatable(a)

pigpigyyy avatar Mar 03 '21 13:03 pigpigyyy

Wow. nice! congrats for the great job.

could this be changed or does it cause conflict?

a.# = __index: tb
mt = a.#

-- TO

a# = __index: tb
mt = a#

aergo20 avatar Mar 03 '21 15:03 aergo20

Yes, it causes conflict. If an expression followed by # symbol means getting its meta table. Codes a.add# will be compiled to getmetatable(a.add) instead of getmetatable(a).__add. So using a.add.# can be different from a.add#.

pigpigyyy avatar Mar 03 '21 15:03 pigpigyyy

the first syntax seemed more consistent to me. # + :, # + ., and # + \

a = close#: =>
b = #:add
c = t#.close
d = t#.close#.test --> getmetatable(getmetatable(t).__close).__test
e = t#\close 1 --> getmetatable(t):__close(1)
f = t#['close'] --> getmetatable(t).close

so it does not cause conflict in the shortcut below

a# = __index: tb
mt = a#

the two underscores could be removed and added automatically here too

a# = index: tb
mt = a#

compiles to

setmetatable(a, {
  __index = tb
})
local mt = getmetatable(a)

literal keys could be added as well

a = close: true, ['close']#: => print "out of scope"

compiles to

local a = setmetatable({
  close = true
}, {
  close = function(self)
    return print("out of scope")
  end
})

that way everything is consistent and beautiful What do you think?

aergo20 avatar Mar 04 '21 15:03 aergo20

I really liked it

a = t#.close --> local a = getmetatable(t).__close
b = t#       --> local b = getmetatable(t)

Classic Lua class using Metamethod syntax:

MyClass = call#: (...) => @.new ...

MyClass.new = (init) ->
  @ = index#: MyClass
  @value = init
  @

MyClass.set_value = (newval) =>
  @value = newval
  
MyClass.get_value = =>
  @value
  
i = MyClass 5
print i\get_value!
i\set_value 6
print i\get_value!

cursos-vc avatar Mar 04 '21 23:03 cursos-vc

I like the idea of ['close']#: => print "out of scope" and got it implemented. And I understand the #., #\ can be used as a new operator. But to add such syntax we have to pick another leading symbol instead of '#', or it will conflict with other syntax like:

with tb
  d = t#.close

Currently compiles to:

local _with_0 = tb
local d = t(#_with_0.close) -- # is an unary operator with a high priority
                                              -- affecting the following expression

So that the version of postfix syntax and indexing table with a '#' symbol that I proposed may work better with the former syntax. Or we have to pick another symbol other than '#' to get the syntax design work.

-- currently implemented
a = close#: =>
b = :add#
c = t.close#
d = t.close#.test# --> getmetatable(getmetatable(t).__close).__test
e = t\close# 1 --> getmetatable(t):__close(1)
f = t.#['close'] --> getmetatable(t).close
a.# = __index: tb
mt = a.#

-- maybe pick up another symbol for a new syntax?
-- for example ` is neither a binary operator nor an unary operator for the moment
a = close`: =>
b = `:add
c = t`.close
d = t`.close`.test --> getmetatable(getmetatable(t).__close).__test
e = t`\close 1 --> getmetatable(t):__close(1)
f = t`['close'] --> getmetatable(t).close
a` = __index: tb
mt = a`

pigpigyyy avatar Mar 05 '21 02:03 pigpigyyy

The main reason for choosing # was the similarity to _ _ and it already exists in Lua, but due to conflicts and consistency it is better to change to your suggestion that looks good. :+1: . Another symbol that I thought was ~

-- set
a = close`: =>
b = `:add

-- get
c = t`.close
d = t`.close`.test --> getmetatable(getmetatable(t).__close).__test
e = t`\close 1 --> getmetatable(t):__close(1) or getmetatable(t).__close(t, 1) ?

-- literal
f = t`['close']

-- shortcut
t` = __index: tb -- alias: a` = `index: tb ?
mt = a`

workaround for the Fill operator (#39)

t1 = {0, key1: 10, key2: 20}
t2 = index`: t1, key3: 30, key4: 40 -- the "index`:t1" is the Fill operator here (uses metatable __index)

print t2[1] -- prints 0
print t2.key1 -- prints 10
print t2.key4 -- prints 40

cursos-vc avatar Mar 05 '21 03:03 cursos-vc

With a little more thought, I feel the first syntax won't be a better choice because there will be 4 rules to memorize for using the metatable syntax and each has a different representation.

a = close`: => -- 1 metamethod '__close'
b = close` -- 2 metatable for close
c = `:close -- 3 metamethod '__ close' and function 'close'
d = t`.close -- 4 metamethod '__close' for t
e = (t`).close -- field 'close' of metatable for t ?

Syntax that is currently implemented won't be this confusing, a name followed by '#' will always refer to the metamethod. And metatable is using dot indexing any expression with '#'.

a = close#: => -- metamethod '__close'
b = close.# -- metatable for close
c = :close# -- metamethod '__ close' and function 'close'
d = t.close# -- metamethod '__close' for t
e = t.#.close -- field 'close' of metatable for t

pigpigyyy avatar Mar 05 '21 08:03 pigpigyyy

Your idea is great. 2 rules make it simple!

cursos-vc avatar Mar 05 '21 15:03 cursos-vc

syntax improvement:

get_mt = () -> {}

a = key: true, #: get_mt!
-- OR - by convention (my favorite) - I haven't checked if it causes conflicts
a = key: true, .#: get_mt!

compiles to

local a = setmetatable({
   key = true
}, get_mt())

how to solve hybrid cases?

-- conditional 'or': (my favorite) - .# always takes precedence
a = key: true, .#: get_mt!, index#: -> print "ok" --> local a = setmetatable({key=true}, get_mt() or {__index = function() return print("ok") end})
a2 = key: true, .#: get_mt!, index#: (-> print "ok"), .#: a2_mt --> local a2 = setmetatable({key=true}, get_mt() or a2_mt or {__index = function() return print("ok") end})

-- parser error:
b = key: true, .#: get_mt!, index#: -> print "ok" --> e. g. ERROR: too many metatable declarations

-- ignore others:
c = key: true, .#: get_mt!, index#: -> print "ok" --> local c = setmetatable({key=true}, get_mt())

this syntax improvement is natural and is an alternative to the current shortcut allowing to unify the keys as well. Both syntaxes have well-defined purposes:

a = .#: mt --> local a = setmetatable({}, mt)
b.# = mt --> setmetatable(b, mt)

his proposal to leave the # symbol as a suffix allowed this improvement. And the surprising thing is that it became cohesive. Thanks!

cursos-vc avatar Mar 08 '21 01:03 cursos-vc

.#: mt and #: mt can be aliases

aergo20 avatar Mar 08 '21 11:03 aergo20

I like this syntax improvement and got it implemented. Hybrid cases are solved with parser error which makes things simpler.

mt = a.#
#:mt = a
a = #:mt
a = #:{__index:mt}

local index
#:{__index:index} = a
:index# = a

do #:{new:ctor, :update} = a
do {new:ctor, :update} = a.#

Now compiles to:

local mt = getmetatable(a)
mt = getmetatable(a)
local a = setmetatable({ }, mt)
a = setmetatable({ }, {
  __index = mt
})
local index
index = getmetatable(a).__index
index = getmetatable(a).__index
do
  local ctor, update
  do
    local _obj_0 = getmetatable(a)
    ctor, update = _obj_0.new, _obj_0.update
  end
end
do
  local ctor, update
  do
    local _obj_0 = getmetatable(a)
    ctor, update = _obj_0.new, _obj_0.update
  end
end

But the new syntax breaks an old syntax of #:abc which is now compiling to setmetatable({ }, abc) instead of #{abc = abc}.

pigpigyyy avatar Mar 08 '21 16:03 pigpigyyy

Hi Li Jin! I'm using this syntax daily in my private projects.

I think the best full syntax was this:

a = a#: true
b = :b#
c = ['c']#: true
d = .#: mt --> code.# instead of #:code here (don't break the old syntax)
e# = e
f = f#
g.# = g --> code.# instead of #:code here
h = h.#
{:i#} = i
{j#: j} = j
{['k']#: k} = k

in short: code.# related to metatable, code# related to metamethod thanks for implementing this!

cursos-vc avatar Mar 18 '21 01:03 cursos-vc

-- Still feeling the current implementation could be more consistent.
-- The old syntax is a rare case, and not to be of much uses. So I think removing it could be acceptable.
a: x = tb
x = tb.a
tb = a: x

#: x = tb
x = tb.#
tb = #: x

get

local x = tb.a
x = tb.a
local tb = {
  a = x
}
x = getmetatable(tb)
x = getmetatable(tb)
tb = setmetatable({ }, x)

pigpigyyy avatar Mar 23 '21 01:03 pigpigyyy

On second thought, the current implementation is more consistent. The old syntax can be removed. I think the syntax is complete. Thank you!

cursos-vc avatar Mar 24 '21 01:03 cursos-vc

Hi, Explicit {} could remove call to setmetatable, useful when you need to create a metatable elsewhere.

a = {close#: => print "out of scope"} --> local a = {__close = function() return print("out of scope") end}
a = close#: => print "out of scope" --> local a = setmetatable({}, {__close = function() return print("out of scope") end})

Example:

a = close: true, close#: => print "out of scope"
b = add#: (left, right)-> right - left
c = key1: true, :add#, key2: true

d, e = a.close, a.close#
{:close#, close#: closeA} = a

f = a\close# 1
a.add# = (x, y)-> x + y

close _ = close#: -> print "out of scope"

-- shortcut for setting and getting the meta table
a.# = {index#: tb} -- old:  __index: tb
mt = a.#

impul-so avatar Jul 25 '21 19:07 impul-so

Better

-- keys and metamethods:
a = b: true, c#: true -- local a = setmetatable({b = true}, {__c = true}) 
-- only keys:
a = b: true -- local a = {b = true}
-- only metamethods:
a = c#: true -- local a = {__c = true}
-- with metatable:
a = b: true, #: d -- local a = setmetatable({b = true}, d)
a = #: {e#: true} -- local a = setmetatable({}, {__e = true})

aergo20 avatar Jul 26 '21 23:07 aergo20

Currently code {key: value} is the same with key: value. So I prefer that {close#: =>} and close#: => could be the same thing. Key name ends with '#' symbol means assigning the metatable field for a table for the moment. So a = c#: true creates an empty table with a metatable having a field named '__c' and a = b: true, c#: true creates a table with a key 'a' and a metatable having a field named '__c'.

a = c#: true -- local a = {__c = true}
-- Change the syntax this way will make it with less 'sugar'.
-- `c#` is just a shortcut for `__c`
a = c#: true -- local a = setmetatable({}, {__c = true})
-- Could save more codes for a syntax sugar

pigpigyyy avatar Jul 27 '21 01:07 pigpigyyy

I agree. My suggestion would just expand this idea with just a syntax. Would save a lot of code, but it would be more complex. My intention is to eliminate the use of __key, replacing it with key#

aergo20 avatar Jul 27 '21 15:07 aergo20

Hi @pigpigyyy

with the new metatable syntax (<>), we can transform all keys, if surrounded by angle brackets, into metamethods

so we will remove the use of double underscores (__key) everywhere.

a = <index: 1, close: 2, "add": 3, [true]: 4>

compiles to

local a = { -- 1
  __index = 1, -- 1
  __close = 2, -- 1
  ["add"] = 3, -- 1
  [true] = 4 -- 1
} -- 1

sample (from docs):

-- create with metatable containing field "value"
tb = <"value">: 123
tb.<index> = tb.<>
print tb.value

tb.<> = __index: {item: "hello"}
 -- new suggestion:
tb.<> = <index: {item: "hello"}>
print tb.item

if angle brackets are placed inside the table again, a syntax error will occur, preventing syntax recurrences:

a = <index: 1, <close>: 2, "add": 3, [true]: 4> -- SYNTAX ERROR! after first ">"

a syntax error should occur in the following code, as it is now

a = <[b: true]>: 1 -- OK
a = <b: true>: 1 -- SYNTAX ERROR!

thanks

1zilla avatar Dec 11 '22 15:12 1zilla

@1zilla It is a nice addition. I like the idea of metamethod key name checking to distinguish angle brackets table with a common table. I will try implementing this into a newer version.

pigpigyyy avatar Dec 12 '22 01:12 pigpigyyy