Here is one more case where an open compiler architecture is a huge plus.
At Mario has created an addin to Boo that gives you the ability to generate from insdie the lagnauge, this one has huge implications. I can see it as a cool way to create mixins. You code use it like this:
[InjectCode(MixinCodeBuilder, "Mixins\Taggable.mixin")]
public class Blog:
[Property] name as string
# Mixins\Taggalbe.mixin
Implements: ITaggable
Code:
Tags:
get:
# get tags
The result of this operation is that the Blog class would implement the ITaggable interface, and all the methods and properties that are specified in the mixin files will be enetered into the Blog class during the compilation process.
This mean that you can (even fromt he Blog class, to treat it as if it implemented ITaggable.
Now that is cool
I've had a fairly strong negative reaction to #Develop in the past, mostly because you really couldn't use if without the IDE started to throw exceptions and refusing to work (my pet peeve was the inability to create & rename new folders, an issue that often require a manual editing of the project file.)
I got a couple of recommendation about #Develop 2, which is currently only available from the Subversion repository (svn://sharpdevelop.net/corsavy/trunk) (or so I understand). I'm writing this review based on revision 619.
Over all, I've got a good feeling about it. The interface is much more proffesional, and it has a built in Boo.
Some things that I didn't like:
- Over eager highlighting and intellisense. It should give me a couple hundred milli seconds of wait before doing that, moving the cursor over the code is like watching fireworks :-)
- Search & Replace has improved tremendously, but I couldn't figure out how to do S&R using regular expressiong with replacing tagged text. In VS.Net, I can use:
seach text: url = {"[^"]+"}
replace with: DoGet(\1)
And it would replace url = "something" to DoGet("something")
The #Develop team has a blog here.
Just check out the syntax to declare a property that is also a primary key:
[Boo.Lang.Property(UserId, Attributes:[PrimaryKey(PrimaryKeyType.Native, "id")])]
The probably proper way to do it would be to ignore the magic Boo.Lang.Property attribute and do it the old fashion way:
userId as int
[PrimaryKey(PrimaryKeyType.Native, "id")]
UserId:
get:
return userId
set:
userId = value
But I find there to be something perversely elegant in the first version.
Update: Thanks to Jake for the grammer correction.
Did you ever had a problem that you simply couldn't solve? I had one such problem 8 months ago, I wanted some way to skip the somewhat tedious log4net logging approach:
logger.Debug("message with "+ "expansive" +" " + "string concentration");
This is the recommended approach from the log4net documentation, and it makes sense, this way, if you don't enable logging, you don't pay for any string concentration. But it's very tedious to do so over and over again. I really wanted a clean way to do it, but at the time Boo's macros were far beyond my capabilities. Even for such a simple task.
Fast forward to the present....
Now I have no difficulities writing this, in fact, the entire utility was developed in one session in Notepad (the way real programmers do it :-) ). What is interesting is that in the meantime I had no education in compilers, AST, etc. I just came back to the subject and it suddenly made sense. It happened to me before in many situations, but this is the first that I can actually find a documented proof of that.
I remember in high school I was unabled to grasp the idea of dynamic memory (on my defense, I didn't try very hard), that was on Pascal with ^ going all over the place. Six months later, I was a triple star programmer (and proud of it :-) ) in C. Again, nothing much has changed, except maybe that this time I was more willing to learn.
Anyway, here is the code, less than 60 lines of code, and I found it useful in reducing common repetitive statements.
""" | |
This macros allows a convenient way to log properly, without getting tired by writing logger.IsXXXEnabled all the time. | |
""" | |
namespace log4net | |
import System | |
import Boo.Lang.Compiler | |
import Boo.Lang.Compiler.Ast | |
class LogdbgMacro(AbstractAstMacro): | |
""" | |
Logs a debug message if debug messages logging are enabled. | |
Usage: logdbg "message" [exception] | |
""" | |
override def Expand(macro as MacroStatement): | |
return GenerateStatement("Debug", macro) | |
class LogwarnMacro(AbstractAstMacro): | |
""" | |
Logs a warning message if warning messages are enabled enabled. | |
Usage: logwarn "warning" [exception] | |
""" | |
override def Expand(macro as MacroStatement): | |
return GenerateStatement("Warn", macro) | |
class LogerrorMacro(AbstractAstMacro): | |
""" | |
Logs an error message if error logging is enabled | |
Usage: logerror "error" [exception] | |
""" | |
override def Expand(macro as MacroStatement): | |
return GenerateStatement("Error", macro) | |
class LogfatalMacro(AbstractAstMacro): | |
""" | |
Logs a fatal error message if fatal error logging is enabled | |
Usage: logfatal "fatal error" [exception] | |
""" | |
override def Expand(macro as MacroStatement): | |
return GenerateStatement("Fatal", macro) | |
class LogInfoMacro(AbstractAstMacro): | |
""" | |
Logs an informative message if informative messages logging is enabled. | |
Usage: loginfo "didja know?" [exception] | |
""" | |
override def Expand(macro as MacroStatement): | |
return GenerateStatement("Info", macro) | |
internal def GenerateStatement(logOption as string, macro as MacroStatement): | |
if len(macro.Arguments) < 1 or len(macro.Arguments) > 2 : | |
raise CompilerError(macro.LexicalInfo, "The log can be called with either one or two parameters only!") | |
isEnabledProp = MethodInvocationExpression( Target: AstUtil.CreateReferenceExpression("logger.get_Is${logOption}Enabled") ) | |
ifEnabled = IfStatement(Condition: isEnabledProp, TrueBlock: Block() ) | |
mie = MethodInvocationExpression(Target: AstUtil.CreateReferenceExpression("logger.${logOption}") ) | |
mie.Arguments.Add(macro.Arguments[0]) | |
mie.Arguments.Add(macro.Arguments[1]) if len(macro.Arguments)==2 | |
ifEnabled.TrueBlock.Add(mie) return ifEnabled |
I have a problem doing AST manipulation at run time, sometimes the code work, and sometimes it doesn't. I know what cause the error, and I can fix it for one scenario, but then the other scenario is borken. I'm pretty sure that I'm doing something nasty, but I don't know what. I'm trying to build a method and reference its arguments, but I keep getting errors when I try to test this.
I'm actually reading raw IL (at least it's better than ASMx86), trying to figure out some things that aren't going well. The problem I've have is with parameter indexing. I've a piece of code that generate other code, and it works if I'm passing it a method with 3 parameters, but not if I pass a method with 4 parameters. Actually, that is not a good explanation, I generate a method (at compile time, using the Boo Compiler AST) based on another method. I add two parameters to this method. The problem is what to do when I'm referencing those parameters. Since the IL reference them by index (ldarg.1, ldarg.s, etc) and not by name (that is for us humans), I'm getting problems about null reference methods on some cases (but only some). The logic I'm using is
Found it!
I started to write this question when I suddenly saw the difference between the two cases, there is something in the asking that makes me suddenly view the problem in a different light. As you can see above, I first thought that this is a problem with the number of the parameters a method has, but the issue was different altogther. One of the methods was static, the other an instance method.
First, some background, what is the difference between an instance and a static method? A static method can't use this, can't access non static resources, etc. It took me so long to figure this out because I didn't notice that one method was static and the other wasn't. An instance method actually get this as a hidden parameter. The practical meaning of this is that for instance methods parameter indexing starts with 1, while parameter indexing for static methods starts with 0. That was the reason for the problem, and after adding three lines, the code is working for both conditions.
Thinking about it, the second I realized that the difference between the method wasn't the number of parameters but the static/instance issue, it was obvious what was causing the problem. (Reading a lot of the IL code didn't hurt, either :-).) I guess I should be thankful that I've experiance in C++, otherwise I would likely still be scratching my head.
Download - http://boo.codehaus.org/Download
Change Log - http://docs.codehaus.org/display/BOO/2005/08/25/boo+0.6+is+here
Lots of goodies, including bug fixes for "my" bugs.
Can someone please explain me why we suddenly got XML all over the place? On the top of my head, we have app.config files, nant build files, AOP configuration, NHiberante mapping files, etc.
Why take an application written in strongly typed language - presumably to allow the compiler to catch your errors - and add weakly typed information that would break your application?
Here is an example of using Boo to script a build file (this is very similar to how you would do it in NAnt, but NAnt is not really design to be used outside of XML and I didn't really have the time to find out the exact contortions I would need to do it in code):
import NAnt.DotNet.Tasks
def Build():
csc = CscTask(Debug: true)
csc.References.BaseDirectory = DirectoryInfo("../lib")
csc.References.Add("MyAssembly.dll")
csc.Sources.Add("**.cs")
Build()
Isn't this nicer then trying to make XML into a programming language? NHiberante allows a nice way to configure it via code, just pass a hashtable with the values. But most of the rest just don't let you do it easily.
I'll probably regret this (I'm currently juggling ~6 projects*), but I'll give you a challange. If you're an application developer and you want to add configuration to your application (that is targeted to developers, of course, if you want user configuration, write a WinForms to do this), just mail me at Ayende@ayende.com and I'll help you add support for configuration in Boo. I'll even show you how to do this in a C# script or Javascript.
* All of them open source, btw.
Sorry for being so silent lately, I've been struggling to get Desing By Contract working for Boo. I got myself into some problems there that I had some really tough time to crack. I'm glad to say that I passed the most major hurdle, which is to get the code to compile the output correctly. It was a touch & go for a while, but I've managed to get it to work as I wanted it.
I've been struggling with this particular problem for a four days or so, trying different work arounds until I found one that work. The code is ugly as hell (and I've a second opinion on that :-) ), but it is working. I'm going to write some test cases* for that and then I'll start making the code more pleasant on the eye.
If you want to read it, the code is available here**, ***.
* Yes, in this particular instance I was walking in to a complete and unknown territory. I wrote code and immediately verified the output myself. I went testless to explor the new territory. Now that I know the lay of the land, I know what I need to test for.
** Not for the faint of heart.
*** This is going to stay up only for a few days, hopefully.