1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
|
The Myrddin Programming Language
Jun 2012
Ori Bernstein
Overview:
Myrddin is designed to be a simple, low level programming
language. It is designed to provide the programmer with
predictable behavior and a transparent compilation model,
while at the same time providing the benefits of strong
type checking, generics, type inference, and similar.
Myrddin is not a language designed to explore the forefront
of type theory, or compiler technology. It is not a language
that is focused on guaranteeing perfect safety. It's focus
is on being a practical, small, fairly well defined, and
easy to understand language for work that needs to be close
to the hardware.
Introduction:
We begin with the archetypical "Hello world" example, deconstructing
it as we go:
use std
const main = {
/* say hello */
std.write(1, "Hello World\n")
}
The first line, `use std`, tells the compiler to import the standard
library, which at the time of this writing only barely exists as a
copy-paste group of files that works only on Linux, implementing almost
no useful functions. One of the functions that it does provide,
however, is the 'write' system call.
The next line, 'const main = ...' declares a constant value called
'main'. These constant values must be initialized at their declaration
to a literal value. In this case, it is intialized to a constant
function '{;std.write(1, "Hello World\n");}'
In Myrddin, all functions begin with a '{', followed by a list
of arguments, which is terminated by a newline (or semicolon. The
two are equivalent). This is followed by any number of statements,
and closed by a '}'.
The text '/* say hello */' is a comment. It is ignored by the compiler,
and is used to add useful information for the programmer. In Myrddin,
unlike many popular languages, comments nest. This makes code like
/* outer /* inner coment */ comment */ valid.
The text 'std.write' refers the 'write' function from the 'std' library.
In Myrddin, a name can belong to an imported namespace. The language,
for reasons of parsimony, only allows one level of namespace. I saw
Java package names and ran screaming in horror, possibly too far to
the other extreme. This function is statically typed, taking a single
integer argument, and a byte slice to write.
The text '(1, "Hello World)' is the function call itself. It takes
the literal "1", and the byte slice "Hello World\n", and calls the
function 'std.write' with them as arguments.
It would be useful now to specify that the value '1' is an integer-like
constant, but it is not an integer. It is polymorphic, and can be used
at any point where a value of any integer type is needed.
Declarations:
In Myrddin, declarations take the following form:
var|const|generic name [: type] [= expr]
To give a few examples:
var x
var foo : int
const c = 123
const pi : float32 = 3.1415
generic id : (@a -> @a) = {a:@a -> @a; -> a}
The first example, 'var x', declares a variable named x. The type is not
set explicitly, but it will be determined by the compiler (or the code
will fail to compile, saying that the type of the variable could not
be determined).
The second example, 'var foo : int' explicitly sets the type of a
variable named 'foo' to an integer. It does not initialize it. However,
it is [FIXME: make this not a lie] a compilation error to use a
variable without prior intialization, so this is not dangerous.
The third example, 'cosnt c = 123' declares a constant named c,
and initializes it to the value 123. All constants require initializers,
as they cannot be assigned to later in the code.
The fourth example, 'const pi : float32 = 3.1415', shows the full form
of declarations. It includes both the type and initializer components.
The final "overdeclared" example declares a generic function called
'id', which takes any type '@a' and returns the same type. It is
initialized to a function which specifies these types again, and
has a body that returns it's argument. This is not idiomatic code,
and is only provided as an example of what is possible. The normal
declaration would look something like this:
generic id = {a:@a; -> a}
Types:
Myrddin comes with a large number of built in types. These are
listed below:
void
The void type. This type represents an empty value.
For reasons of consistency when specializing generics, void
values can be created, assigned to, and manipulated like
any other value.
bool
A Boolean type. The value of this is either 'true' (equivalent
to any non-zero) or 'false', equivalent to a zero value. The
size of this type is undefined.
char
A value representing a single code point in the default
encoding. The encoding is undefined, and the value of the
character is opaque.
int8 int16 int32 int64 int
uint8 uint16 uint32 uint64 uint
Integer types. For the above types, the number at the end
represents the size of the type. The ones without a number at
the end are of undefined type. These values can be assumed to
be in two's complement. The semantics of overflowing are yet to
be specified.
float32 float64
Floating-point types. The exact semantics are yet to be
defined.
@<name>
A generic type. This is only allowed in the scope of 'generic'
constants.
It also allows composite types to be defined. These are listed below:
<type>*
A pointer to a type This type does not support C-style pointer
arithmetic, indexing, or any other such manipulation. However,
slices of it can be taken, which subsumes the majority of uses
for pointer arithmetic. The pointer is passed by value, but as
expected, the pointed to value is not.
<type>[,]
A slice of a type. Slices point to a number of objects. They
can be indexed, sliced, and assigned. They carry their range,
and can in principle be bounds-checked (although the compiler
currently does not do this, due to the lack of a runtime library
that will allow a 'panic' function to be called).
<type>[size]
An array of <type>. Unlike most languages other than Pascal, the
size of the array is a part of it's type, and arrays of
different sizes may not be assigned between each other. Arrays
are passed by value, and copied when assigned.
<type0>,<type1>,...,<typeN>
A tuple of type t0, t1, t2, ....
Finally, there are aggregate types that can be defined:
struct
union
Any of these types can be given a name. This naming defines a new
type which inherits all the constraints of the previous type, but
does not unify with it. Eg:
type t = int
var x : t
var y : int
x = y // type error
x = 42 // sure, why not?
Type Constraints
Literals:
character
bool
int
float
func
sequence
Symbols
Imports
Exports
|