Summary
Cobra 0.7 adds class level invariant contracts, support for .NET attributes, out and inout arguments, event listening, a FORTH interpreter and fixes another round of bugs.
Language Improvements
invariant
In continued support of Eiffel-style contracts, Cobra 0.7 adds the optional invariant clause to class, interface and struct declarations:
Invariants are executed at the end of all initializers, methods, properties and indexers similar to the ensure clause of such members. Invariants are useful in at least the following ways:
- They help document the behavior of a class.
- They ensure objects are created in a valid state.
- They catch errors earlier when they are easier to diagnose.
Attributes
The .NET platform supports having "attributes" attached to declarations which can be used by compilers or through reflection for various effects. For example, some testing frameworks use attributes to mark which methods contain test code. In Cobra, attribute declarations are preceded by has which answers the question "What attributes does the method have?" A: "It has ..."
Some rules about attributes:
- The trailing "Attribute" portion of the attribute class name can be omitted.
- No parentheses are necessary if no arguments are passed.
- Multiple arguments can be passed to the attribute, separated by commas.
- Properties of the attribute can be set within the call using the assignment syntax seen above.
Abstract Classes
Classes and their members can be marked abstract like so:
Some rules about abstract classes and members:
- Interfaces and structs cannot be marked abstract.
- Abstract classes cannot be instantiated.
- Subclasses must be marked abstract or provide implementations for all inherited abstract members.
- Methods, properties and indexers—collectively referred to as "members"—can all be marked abstract.
- Initializers cannot be marked abstract.
- Abstract members can have contracts and unit tests, but no body code.
- Concrete implementations of members must be marked is override and have the same arguments and return type.
In less formal terms, abstract classes help document the intent of the class. They also relieve you of having to create dummy implementations of abstract methods that return meaningless values or throw exceptions. Finally, because they are a language level feature, the compiler can assist you by blocking the instantiation of such classes or by listing abstract members you forgot to implement in your concrete subclass.
out and inout
The .NET platform supports pass-by-reference arguments. Cobra now understands the methods that use them in libraries and can declare them as well. A common use for them is to "return" multiple values as seen here:
Events
Cobra can now listen to and subsequently ignore .NET events:
Note the use of ref (for "reference" or "refer to") as a prefix to prevent method invocation. As a prefix, it also aids readability by indicating immediately that what follows is a reference and not an invocation.
Language and Library Refinements
- Enabled the pro/get/set foo from var shortcut syntax to have "is names":
- Relaxed the restriction that interface names had to start with a capital "I".
- Added a warning when the binary is or is not operators are used on value types such as int. Use the value-oriented operators == and <> instead.
- Added a new warning for extraneous empty parens on method calls and declarations.
- Added CobraCore.printDestination which returns the current destination for print statements from the "print destination stack". "What's that?" you say? It's what Cobra maintains when you write:
Now at any point, including inside .computeStuff, you can write CobraCore.printDestination.flush or some such.
- Generate #line in the C# code so that runtime stack traces will have correct filename and line number information.
Wow, this was overdue! I looked for #file a long time ago but didn't find it so never realized there was, in fact, a #line directive in C# that optionally took the file name. Now if you type "cobra -d myprogram" and it throws an exception you will see a stack with the correct file names and line numbers.
Command Line Improvements
- Changed the command line option delimiter from "=" to ":" as in "-r:System.Data". This matches other command line tools (mcs, nant, csc).
- Added -sharp-args argument for passing arguments down to the C# compiler. This makes a nice escape hatch if you need something that Cobra has not yet implemented.
cobra -sharp-args:'-main:Bar' foo.cobra
- Added -library-directory (or -lib) option used to add search directories for libraries.
- Added -d as a synonym for -debug
- Turned exception report off by default since it's needed less often now that Cobra bytecode has correct file name and line number information. You can turn it back on with -exception-report.
- Also, unhandled exceptions are now accompanied not only by their stack trace, but also some Cobra debugging tips. You can turn this off with -debugging-tips:no.
- When passing -editor or using the COBRA_EDITOR environment variable, exceptions from launching the editor process are handled gracefully.
- Tip: If you're on Mono and you're running a Cobra .exe with the "mono" command and you compiled with -d/-debug then be sure to pass —debug to mono to get the source information. Like this:
$ cobra -d -c foo.cobra bar.cobra $ mono --debug foo.exe
But if you don't need the separation between compiling and running, you can do both in one shot:
$ cobra -d foo.cobra bar.cobra
- Fixed: Specifying -embed-run-time and -detailed-stack-trace caused StackOverflow in the Cobra program.
Sample Programs
Added forth.cobra which interprets a subset of the stack-based language FORTH. Just for fun.
In Sizes.cobra, removed a stray assert false and fixed various warnings. Also, made it recover gracefully from I/O errors.
Fixes
- Fixed: Commenting out a line in the middle of a branch block causes a false parser error.
- Fixed: Casting to an instantiation such as a to List<of int>() causes an internal error instead of a normal error message.
- Fixed: Assigning a list to variable where an element of the list has an error, causes an internal error.
- Fixed: Assigning to a class level variable in an if-inherits statement regarding that same variable, leaves the variable unchanged.
- Fixed: Using typecasting (to) in an expression that initializes a class variable causes an internal error.
- Fixed: Initializing an explicitly typed class-level variable ("var _o as Object = 1") types the variable from the expression when it should be typed from the explicit type.
Technical Notes
- Change test invocations to be triggered by reflection traversal instead of static initializers.