Boo

Macro named (and typed) arguments

Details

  • Type: New Feature New Feature
  • Status: Closed Closed
  • Priority: Major Major
  • Resolution: Fixed
  • Affects Version/s: 0.9
  • Fix Version/s: 0.9.1
  • Component/s: Compiler
  • Labels:
    None
  • Testcase included:
    yes
  • Number of attachments :
    0

Description

Current macro macro: syntax is a little unfriendly when it comes to handling and checking arguments.
Either you manually do a lot of manual checks and casting, either you use pattern matching to match arguments, but it is somewhat non-wrist-friendly to do it for simple patterns).

Macro definitions now support arguments definition in a wrist-friendly and intuitive way, including handling of variable number of (optional) arguments.
To match a variable number of arguments use T* (enumerable) notation (e.g 'arg as string*' for a variable number of string arguments).
To match argument type against macro's body, just name the argument `body` (e.g 'body as MethodInvocationExpression*' for allowing only method invocation expressions within the body).

Testcase (and documentation ):

"""
the sum of those 6 numbers is 108
foofoo
barbar
invocation #1 of 2
invocation #2 of 2
Line 'How do you bou?' contains unknown word 'bou'. Did you mean 'boo'?
"""
import System
import Boo.Lang.Compiler.Ast
import Boo.Lang.PatternMatching

macro sum(numbers as int*):
	s = 0
	for n in numbers:
		s += n
	yield [| print "the sum of those ${$(numbers.Count)} numbers is ${$s}" |]

macro repeatLines(repeatCount as int, lines as string*):
	for line in lines:
		yield [| print $line * $repeatCount |]

macro invokeWithCount(body as MethodInvocationExpression*):
	for invocation in body:
		invocation.Arguments.Add([| $(body.Count) |])
		yield invocation

macro spellCheck(lang as string, body as string*):
	raise "Unknown language `${lang}`" if lang != "en-EN"
	for line in body:
		if line.Contains("bou"):
			yield [| print "line '${$line}' contains unknown word 'bou'. Did you mean 'boo'?" |]


sum 4, 8, 15, 16, 23, 42

repeatLines 2, "foo", "bar"

invokeWithCount:
	Console.WriteLine("invocation #1 of {0}")
	Console.WriteLine("invocation #2 of {0}")

spellCheck "en-EN":
	"Hello boo!"
	"How do you bou?"

Issue Links

Activity

Hide
Rodrigo B. de Oliveira added a comment -

The text misrepresents the way macros are expressed Today. The first macro could be:

macro printTimes:
	case [| printTimes $(StringLiteralExpression(Value: text)), $(IntegerLiteralExpression(Value: count)) |]:
		for i in range(count):
			yield [| print $text |]
Show
Rodrigo B. de Oliveira added a comment - The text misrepresents the way macros are expressed Today. The first macro could be:
macro printTimes:
	case [| printTimes $(StringLiteralExpression(Value: text)), $(IntegerLiteralExpression(Value: count)) |]:
		for i in range(count):
			yield [| print $text |]
Hide
Cedric Vivier added a comment - - edited

Oh yeah nice
I did not think about pattern matching for such simple patterns, but well indeed it could be implemented that way by macro `macro`.

The point still, is or isn't the syntax proposal a good idea?
I mean pattern matching is really nice of course but for such simple patterns it seems a little overkill to have to write these isn't it?
Also the new syntax can potentially provide better error reporting (as in display the 'macro' arguments like "Usage: macroname arg1 arg2..")

The nice thing about implementing this syntax with pattern matching would be to be able to write macro overloads in isolation:

macro option(name as string, value as string):
         yield [| print "<string name="${$name}">${$value}</string>"  |]

macro option(name as string, value as int):
         yield [| print "<integer name="${$name}">${$value}</string>"  |]

would produce simply two cases merged in the same 'option' macro

macro option:
	case [| option $(StringLiteralExpression(Value: name)), $(StringLiteralExpression(Value: value)) |]:
               yield [| print "<string name="${$name}">${$value}</string>"  |]

	case [| option $(StringLiteralExpression(Value: name)), $(IntegerLiteralExpression(Value: value)) |]:
               yield [| print "<integer name="${$name}">${$value}</integer>"  |]
Show
Cedric Vivier added a comment - - edited Oh yeah nice I did not think about pattern matching for such simple patterns, but well indeed it could be implemented that way by macro `macro`. The point still, is or isn't the syntax proposal a good idea? I mean pattern matching is really nice of course but for such simple patterns it seems a little overkill to have to write these isn't it? Also the new syntax can potentially provide better error reporting (as in display the 'macro' arguments like "Usage: macroname arg1 arg2..") The nice thing about implementing this syntax with pattern matching would be to be able to write macro overloads in isolation:
macro option(name as string, value as string):
         yield [| print "<string name="${$name}">${$value}</string>"  |]

macro option(name as string, value as int):
         yield [| print "<integer name="${$name}">${$value}</string>"  |]
would produce simply two cases merged in the same 'option' macro
macro option:
	case [| option $(StringLiteralExpression(Value: name)), $(StringLiteralExpression(Value: value)) |]:
               yield [| print "<string name="${$name}">${$value}</string>"  |]

	case [| option $(StringLiteralExpression(Value: name)), $(IntegerLiteralExpression(Value: value)) |]:
               yield [| print "<integer name="${$name}">${$value}</integer>"  |]
Hide
Cedric Vivier added a comment - - edited

Macro expected body can be defined when last argument name is `body`.
Also enumerable (or array) argument type is allowed for last argument (whether or not `body`), this means variable number of arguments (or variable number of statements for `body` argument) is allowed, as long as it matches the specified type (if any).

E.g:

macro printOnlyCorrectlySpelled(body as string*):
     for sentence in body:
          if SpellCheck(sentence):
                yield [| print $sentence |]

printOnlyCorrectlySpelled:
     "one long big sentence..............."
     "one anoother......................................"
Show
Cedric Vivier added a comment - - edited Macro expected body can be defined when last argument name is `body`. Also enumerable (or array) argument type is allowed for last argument (whether or not `body`), this means variable number of arguments (or variable number of statements for `body` argument) is allowed, as long as it matches the specified type (if any). E.g:
macro printOnlyCorrectlySpelled(body as string*):
     for sentence in body:
          if SpellCheck(sentence):
                yield [| print $sentence |]

printOnlyCorrectlySpelled:
     "one long big sentence..............."
     "one anoother......................................"
Hide
Cedric Vivier added a comment - - edited

Testcase (macro-arguments-1.boo):

"""
Boo
Rocks!
So true!
Assertion that `b` is true failed!
Assertion that expressions `a.ToString()` and `bool.FalseString` are equal failed!
Regex 'foo' matched string 'foofoo'
86423 seconds (1.00:00:23) is longer than one day.
"""
import Boo.Lang.PatternMatching

macro printYOnlyIfZIs42(x as string, y as string, z as long):
	yield [| print $x |]
	yield [| print $y |] if z == 42


macro printOnlyIfTrue(text as string, boolean as bool):
	yield [| print $text |] if boolean


macro assertTrue(variable as Boo.Lang.Compiler.Ast.ReferenceExpression):
	yield [| print "Assertion that `${$(variable.Name)}` is true failed!" if not $variable |]


macro assertEqual(a,b):
	yield [|
		if $a != $b:
			print "Assertion that expressions `${$(a.ToCodeString())}` and `${$(b.ToCodeString())}` are equal failed!"
	|]


macro matchRegex(pattern as regex, text as string):
	yield [| print "Regex '${$pattern}' matched string '${$text}'" |] if pattern.IsMatch(text)


macro longerThanOneDay(duration as timespan):
	yield [| print "${$(duration.TotalSeconds)} seconds (${$duration}) is longer than one day." |] if duration > 1d


printYOnlyIfZIs42 "Boo", "Rocks!", 42L

printOnlyIfTrue "So false!", false
printOnlyIfTrue "So true!", true

a = true
b = false
assertTrue a
assertTrue b

assertEqual a.ToString(), bool.TrueString
assertEqual a.ToString(), bool.FalseString

matchRegex /foo/, "foofoo"
matchRegex /foo/, "barbar"

longerThanOneDay 86423s
longerThanOneDay 23h
Show
Cedric Vivier added a comment - - edited Testcase (macro-arguments-1.boo):
"""
Boo
Rocks!
So true!
Assertion that `b` is true failed!
Assertion that expressions `a.ToString()` and `bool.FalseString` are equal failed!
Regex 'foo' matched string 'foofoo'
86423 seconds (1.00:00:23) is longer than one day.
"""
import Boo.Lang.PatternMatching

macro printYOnlyIfZIs42(x as string, y as string, z as long):
	yield [| print $x |]
	yield [| print $y |] if z == 42


macro printOnlyIfTrue(text as string, boolean as bool):
	yield [| print $text |] if boolean


macro assertTrue(variable as Boo.Lang.Compiler.Ast.ReferenceExpression):
	yield [| print "Assertion that `${$(variable.Name)}` is true failed!" if not $variable |]


macro assertEqual(a,b):
	yield [|
		if $a != $b:
			print "Assertion that expressions `${$(a.ToCodeString())}` and `${$(b.ToCodeString())}` are equal failed!"
	|]


macro matchRegex(pattern as regex, text as string):
	yield [| print "Regex '${$pattern}' matched string '${$text}'" |] if pattern.IsMatch(text)


macro longerThanOneDay(duration as timespan):
	yield [| print "${$(duration.TotalSeconds)} seconds (${$duration}) is longer than one day." |] if duration > 1d


printYOnlyIfZIs42 "Boo", "Rocks!", 42L

printOnlyIfTrue "So false!", false
printOnlyIfTrue "So true!", true

a = true
b = false
assertTrue a
assertTrue b

assertEqual a.ToString(), bool.TrueString
assertEqual a.ToString(), bool.FalseString

matchRegex /foo/, "foofoo"
matchRegex /foo/, "barbar"

longerThanOneDay 86423s
longerThanOneDay 23h
Hide
Cedric Vivier added a comment - - edited

Initial support (handles testcase macro-arguments-1 above) landed in rev. 3231+3232

Next steps:

  • `body` argument to match against macro.Body
  • enumerable/arrray argument types
  • macro overloads
Show
Cedric Vivier added a comment - - edited Initial support (handles testcase macro-arguments-1 above) landed in rev. 3231+3232 Next steps:
  • `body` argument to match against macro.Body
  • enumerable/arrray argument types
  • macro overloads
Hide
Cedric Vivier added a comment -

Fully landed in rev. 3244
Forked macro overloading to BOO-1156

Show
Cedric Vivier added a comment - Fully landed in rev. 3244 Forked macro overloading to BOO-1156

People

Vote (0)
Watch (0)

Dates

  • Created:
    Updated:
    Resolved: