Tuesday, November 28, 2006

scopes and RAII

Classes in Hyper cannot have a destructor. I wanted it to be this way because the language uses a garbage collector, and then the execution of a destructor cannot be always guaranteed. So destructors would not be reliable anyway if you want it to release acquired resources. And without destructors there is currently no way to do RAII. This is not acceptable in my opinion. So we need at least one way to do it.

This is my proposition. I would like to introduce a new block statement, "scope". This would take care of resource acquisition and disposal. You would give it an object that support 2 methods: "enterScope()" and "leaveScope()". The scope block would call the first method upon entering the scope, and would make sure that the second one is called whenever execution leaves the scope (i.e. normal exit at the end, plus exceptions and jump statements like "return", "break", etc.). The first form of "scope" would support the declaration of a variable, with or without an initializer. But I think it would also be useful to have an anonymous variable (i.e. to give it no name) and/or to have no variable at all and simply use a temporary object on the stack.

Examples:

procedure test(x : * Xyz)
# indented output
scope i : Indenter = x.getIndenter() # increase indentation
i.printLn("Hello world.")
# ...
end # get indentation back to the previous level

scope this.getMutex() # lock mutex
# ...
end # unlock mutex
end test

The first example uses an explicit variable and the second uses no variable at all. In the second example the mutex returned by "getMutex()" is used as the scope object. In this case the result of the scope expression is probably a pointer (or a reference) to a field, but it does not necessarily has to be a pointer.

This reminds me about how to treat temporaries. I suggest to let them exist until the function they appear in returns. This would be ideal for use with restricted pointers. Any temporary could then be pointed to by a restricted pointer. And those could be stored in variables, so it would be necessary to keep the temporaries alive until the function returns.

Monday, November 27, 2006

operations on numeric types

First a bit of info about the built-in numeric types, in case you never saw them before or in case you have forgotten. The floating point types: 'single', 'double' and 'real' (not the subject of this article). The integral types: 'byte', 'nat16', 'nat32', 'nat64', 'int16', 'int32', 'int64' and 'int'. A 'byte' is unsigned and 8 bits. The 'int*' types are signed integer types (specified size or native int type), 'nat*' are unsigned integer types (again, of the specified size or else the native size). The native ones, 'int' and 'nat' are equal in size and have the size of the target platform (32 or 64 bit).

I am still somewhat unsure about the operations on those types. For example: what type should the result of a unary minus on a 'nat16' be? I would say 'int32', because the result can require 17 bits and that would not fit into a 'int16'. And I want to avoid unexpected overflows as much as I can. But this leaves me with some unpleasant consequences. I have no type to use for the unary minus on a 'nat64'. And neither I have for the native 'nat', because it would require one more bit than the 'int' has. So at this time there is no unary minus available for those two types. This problem of course doesn't exist for the 'int*' types; for example the unary minus operation on a 'int16' returns an 'int16' again.

I propose the following 'solution': I give the 'nat*' types a member function 'truncateToSigned', or something like that, which discards the most significant bit and then returns a signed type of the same size as the original. Like this:

class nat16
public:
# (...)
const procedure truncateToSigned() : int16
# truncate and convert to signed
end
end

This allows the programmer to do what he/she wants, but allows for data loss. But at least the 'corruption' is visible by looking at the name of the function!

I already provided the 'int*' types with a function to turn the sign. This is a way to write assignments like 'delta = -delta' like this: 'delta.turnSign' (will be available in the next compiler version). This function doesn't return a result and changes the value of the object itself. (So it is different from the unary minus, which does not change its object and instead returns a value.) I wasn't completely sure about the name, maybe I could have used something like 'negate' instead but I am not sure if that means what's intended (I am not a native English speaker).

I am thinking about a signed byte type. At this type the literal -1 has type int16. That looks like a bit of a waste for such a small number. So it would maybe be nice to add a type (e.g. 'sbyte') for unsigned 8-bit values. Then I could add to 'byte' also a member 'truncateToSigned' to return a signed equivalent. But to avoid loss the unary minus of 'byte' would still return an 'int16'.

Another proposition. We now have a way to make a signed from an unsigned but not the other way around. So I would like to add a member 'abs' (= absolute value) that gives the unsigned value. I don't really like 'abs' because it looks to short. But on the other hand, something like 'absoluteValue' looks too long. Suggestions and argumentations for a name are of course welcome :-)


P.S.: I converted my blog to the new Blogger Beta, and it looks like the RSS of the old articles is a bit messed up now

Sunday, October 29, 2006

Hyper compiler 0.3.30 released

I've just released a new version of the compiler for Hyper. You can download it from here.

A short list of changes in this release:
  • highlighter now uses the same style as the Hyper website
  • copy constructors are now checked and auto-generated if needed
  • 'inout' is now used for parameters that used 'var'
  • the 'this' keyword is now supported in expressions
  • access specifiers in a namespace are no longer supported
And next to these changes, the compiler has been restructured internally a bit as well.

Friday, October 27, 2006

begin specification

Something that is not documented yet on the website is the begin directive. It appears on top of the source file, to indicate the entry point for that file if it is compiled as an executable. You specify the class that should be 'started', the class that contains the static procedure called `main'. This procedure will be called when the executable is run.

It's use was mandatory, but I am now making it optional in one case: when the file contains only one class. Because in that case the user's intention is obvious. This simplifies the Hello World example:
namespace Example
class Hello
static procedure main()
system.out.printLn("Hello, World!")
end
end
end
This Hello World program will change even more later. Namespaces will be removed and some module system, or a system like Java's packages will be adopted. And the function (or 'procedure') to output text to the console will probably be renamed.