Boo
  1. Boo
  2. BOO-1146

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

            • Assignee:
              Cedric Vivier
              Reporter:
              Cedric Vivier
            • Votes:
              0 Vote for this issue
              Watchers:
              0 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved: