Veeyu is a general-purpose functional and object-oriented programming language most influenced by Lisp family and Python. It provides lambda functions with lexical scope, runtime polymorphism, well designed modern object protocol that supports metaclasses, macro facilities and homoiconicity.
The minimal goal of Veeyu is to provide an extensible homoiconic language without S-expressions. It was influenced by Dylan, Io, Python and Lisp mostly.
This document specifies Veeyu’s small syntax, semantics and runtime libraries plus some test codes. It’s okay to treat this document as the specification of Veeyu.
You can find its source code from the Git repository:
git clone git://github.com/veeyu/veeyu.git
You can check it from the web also: https://github.com/veeyu/veeyu
Veeyu has a simple syntax. There are only few keywords of punctuations like =
, :=
, .
and parentheses (and no keyword if keywords mean roman words). All types of forms are expressions that are homoiconic. Its syntax is slightly less minimal than Lisp, but still enough simple.
Veeyu syntax consists of several types of forms. All forms are expressions; that is, all forms return some value. (That’s why there’s no explicit return statement in Veeyu unlike other programming languages.)
A Veeyu program is composed of zero or more forms. A semicolon character (;
) or a line break is used for a boundary between forms.
<source-code> ::= [ <shebang-line> ] <program>
<shebang-line> ::= "#!" { <any character> - <newline> } <newline>
<program> ::= { <form-separator> }
{ <form> <form-separator> { <form-separator> } }
{ <form-separator> }
<form-separator> ::= ";" | <newline>
A program has to be a Unicode string, not a byte string. Typically it is stored in a file, and the program reader i.e. parser reads it from the file. But because files store byte strings, not Unicode strings, so the program reader treats files as UTF–8 string by default. And program readers may implement following optional advantages:
-e
/--encoding
option from the shebang line.For example, an implementation may guess that the following Veeyu program is encoded by EUC-KR.
#!/usr/bin/env veeyu -eeuc-kr
write-line!: "안녕, 比喩!"
A program can start with shebang line, the #!
syntax used in scripts to indicate an interpreter for execution in Unix, and then its first shebang line is just ignored. It is not a general comment in Veeyu, but just a character sequence ignored only in the beginning of the program. So, for example, the following program causes <name-error>
, because #!/usr/bin/env
is parsed to a symbol which is undefined.
"""The first line is not a shebang."""
#!/usr/bin/env veeyu -eascii
In order to use a symbol that starts with #!
in the beginning of program, keep the first line empty or insert a meaningless shebang line.
#!/usr/bin/env veeyu
"""Shebang-line test."""
#!symbol := 123
The above example defines #!symbol
, is 123
.
As above examples showed, an implementation may provide several options through shebang line. For example, if an implementation is a compiler, it may have a -r
/--restrict
option or a -e/--extension
for bootstrapping.
$!/usr/bin/env veeyu --restrict=eval,typeinfer --extension=partialclass
require: partial := veeyu.ext.partialclass
partial.extend!(<attribute-name>,
+apply: method (environment: <object>) {
environment.+get-attribute(self)
})
partial.extend!(<attribute>,
+apply: method (environment: <object>) {
self.receiver(environment).+get-attribute(self.attribute)
})
partial.extend!(<quote>,
+apply: method (environment: <object>) {
self.form(environment)
})
partial.subclass!(<form>, <function>)
There are several types of forms:
apple
, <fruit>
apple.color
:apple
, :(eat(apple))
123
, 3.14
'Avishai Cohen'
, "Structure in Emotion"
eat(apple, with: fork)
[1, 2, 3, 4]
, [x * 3, x: 1 ~ 100, x +isa? <odd-number>]
pi := 3.14
, a = a + 1
{ eat(apple); eat(apple, with: fork) }
Read further for details.
<form> ::= <symbol> | <attribute> | <quote>
| <number-literal> | <string-literal>
| <call> | <index> | <definition> | <block>
Symbols are used for identifiers. A symbol can be most of words e.g. value
, <abstract-class>
, +
, ==
. However, it’s not there is no constraint for symbols.
Cannot be =
or :=
. Because these are treated as definition operators.
Cannot include any whitespace characters e.g. like this
.
Cannot include period character (.
) e.g. like.this
. Because these are treated as attributes.
Cannot consist of only digits even if a hyphen character (-
) leads e.g. 123
, -123
. Because these are treated as number literals.
Cannot include colon character (:
) e.g. :like-this
, like-this:
. Because symbols with a leading colon are treated as symbol quotes, and symbols with a trailing colon are treated as function/macro calls.
Cannot include any of parentheses, square brackets or curly brackets e.g. like(this)
, like[this]
, like{this}
. Because these are treated as function/macro calls, indices or blocks.
Cannot include commas (,
) e.g. like,this
. Because commas are already reserved for separation of arguments in a function/macro call.
Cannot include semicolon characters (;
) e.g. like;this
. Because semicolons are already reserved together with new line characters for separation of statements.
Cannot include single quotes ('
) or double quotes ("
) e.g. like-this'
, like"this"
. Because these quotation marks are already reserved for string literals.
Though there are above many constraints, practically it is not a problem and symbols are expressive enough. For example, you can append a trailing question mark (?
) into names of predicate functions like is-okay?
.
Symbols can include underscore characters (_
) like other programming languages, but Veeyu prefer instead hyphen characters (-
) for separation of words according Lisp tradition.
<symbol> ::= ( <sym-char> - ( <digit> | "=" ) ) { <sym-char> }
| "=" <sym-char> { <sym-char> }
<sym-char> ::= <any charactr> -
( <whitespace> | ":" | "." | "," | ";" | "(" | ")" |
"[" | "]" | "{" | "}" | '"' | "'" )
Every value (object) has some attributes. We can say that physically every object is just a key-value storage (where keys are surely attribute names) which remembers its class. And there’s a syntax to access to it.
a-value.an-attribute
Attribute syntax is like the above code: the first receiver form (a-value
in the example) comes, a middle period character (.
) follows, and the trailling attribute name (an-attribute
in the example) ends. If whitespaces close to the middle period character present, they are ignored.
a-value . an-attribute
a-value. an-attribute
a-value .an-attribute
New line characters follows the middle period character are acceptable.
a-value.
an-attribute
Attribute name can be either a symbol, an index or a block.
a-value.symbol-attribute
a-value.[index-attribute]
a-value.{ block-attribute }
Middle period character can be omitted if the attribute name is an index or a block.
a-value[index-attribute]
a-value { block-attribute }
Attribute access is internally a call of +get-attribute
method. For example, the following three forms:
receiver.symbol-attribute
receiver[index-attribute]
receiver { block-attribute }
and following three forms:
receiver.+get-attribute(:symbol-attribute)
receiver.+get-attribute(:([index-attribute]))
receiver.+get-attribute(:({ block-attribute }))
share the same behavitor.
<attribute> ::= <form> "." <attribute-name>
| <form> ( <index> | <block> )
<attribute-name> ::= <symbol> | <index> | <block>
Quotes are similar to Lisp’s quotes. A leading colon character and trailing parentheses that wrap a form are quote forms. They are evaluated as form objects.
:(form)
:(1)
:(complex(form))
:identifier
Parentheses can be omitted when a quoted form is an symbol.
<quote> ::= ":(" <form> ")"
| ":" <symbol>
Number literals represents <integer>
and <float>
instances like 123
and 3.14
. It includes a minus sign, because Veeyu has no unary operator. (That’s why symbols cannot be decimal string even a hyphen leads like -123
.) But there’s no plus sign for number literals.
Example | Type it means |
---|---|
0 |
<natural-number> |
123 |
<positive-integer> |
-123 |
<negative-integer> |
3.14 |
<positive-float> |
-3.14 |
<negative-float> |
123.0 |
<positive-float> |
-123.0 |
<negative-float> |
3.14e-10 |
<positive-float> |
Number literals accept decimal numbers only. Hexadecimal or octal numbers can be represented by string literals with prefix characters.
<number-literal> ::= <integer-literal> | <float-literal>
<integer-literal> ::= [ "-" ] <digit> { <digit> }
<float-literal> ::= <integer-literal> "." <digit> { <digit> }
| <exponent-literal>
<exponent-literal> ::= <integer-literal> [ "." <digit> { <digit> } ]
[ "e" | "E" ] [ "+" | "-" ] <digit> { <digit> }
The main purpose of string literals is to represent string values by hardcoding in source codes. There are four types of string literals:
Types | Single-quoted | Double-quoted |
---|---|---|
Singleline | 'str' |
"str" |
Multiline | '''str''' |
"""str""" |
There are special escaping sequences for double-quoted strings: most of C-style escape sequences.
Escape sequence | Meaning (Unicode code point) |
---|---|
\\ |
Backslash (\ , U+005C) |
\' |
Single quote (' , U+0027) |
\" |
Double quote (" , U+0022) |
\a |
ASCII Bell (BEL, U+0007) |
\b |
ASCII Backspace (BS, U+0008) |
\f |
ASCII Formfeed (FF, U+000C) |
\n |
ASCII Linefeed (LF, U+000A) |
\r |
ASCII Carriage Return (CR, U+000D) |
\t |
ASCII Horizontal Tab (TAB, U+0009) |
\v |
ASCII Vertical Tab (VT, U+000B) |
\x?? |
Character with hexadecimal code point (U+00??) |
\u???? |
Character with hexadecimal code point (U+????) |
\U???????? |
Character with hexadecimal code point (U-????????) |
Single-quoted strings can be named as raw string also; escape sequences described above are interpreted just literally in single-quoted strings.
It can also have a prefix character is a case-sensitive roman character i.e. [A-Za-z]
in a regular expression, so it can be used for representing another types of values.
Prefix character | Meaning | Example |
---|---|---|
b |
Binary number | b'101010' |
c |
Character | c'*' |
x |
Hexadecimal number | x'2a' |
w |
Whitespace-separated list | w'life universe everything' |
The type of an evaluated string literal can be vary depending on its prefix character, but its type is invariable in the same prefix character. For example, string literals with the prefix character c
are evaluated as a <character>
object.
Any other prefix characters not described in the above table of prefix characters can be user-defined or extended by the specific implementation but defining a prefix character is not preferred because of forward compatibility.
How to define an operation of prefix character is just to define +literal-x-type
and +literal-x
function in the environment where x
is a prefix character to define its operation.
+literal-r-type := regex.<pattern>
+literal-r := regex.compile
For example, the above code defines a prefix character r
, then a form r'^[a-z]'
will be evaluated to an instance of regex.<pattern>
class, that is a return value of a call regex.compile('[^a-z]')
. Evaluating a string literal with undefined prefix character cause a <literal-error>
, a subclass of <syntax-error>
.
If a return value of a call the function +literal-x
is not an instance of +literal-x-type
(or its subtype), it raises a <type-error>
.
<string-literal> ::= [ <string-literal-prefix> ]
<string-literal-body>
<string-literal-prefix> ::= <alpha>
<string-literal-body> ::= <string-literal-doublequote-body>
| <string-literal-doublequote-body>
<string-literal-dq-body> ::= '"' { <string-item> } '"'
:: <string-literal-quote> ::= '"'
<string-literal-sq-body> ::= "'" { <string-item> } "'"
:: <string-literal-quote> ::= "'"
<string-literal-tdq-body> ::= '"""' { <string-item> } '"""'
:: <string-literal-quote> ::= <no character>
<string-literal-tsq-body> ::= "'''" { <string-item> } "'''"
:: <string-literal-quote> ::= <no character>
<string-literal-quote> ::= '"' | "'"
<string-item> ::= <string-character> | <string-escape-sequence>
<string-character> ::= <any character> - "\" - <string-literal-quote>
<string-escape-sequence> ::= "\" <any character>
The call, the calling form is either a function application, a macro expansion or a fexpr call. It takes zero or more arguments wrapped with parentheses, typically, in the case of normal calling form.
There are three types of calling forms.
A traditional C-style calling form e.g. function(arguments)
, function()
. It can takes positional arguments and named arguments both.
A calling form using only beginning colon character instead of wrapping parentheses pair e.g. function: arguments
. There is a limit that named arguments cannot be applied for simple calling forms and it must one or more positional arguments.
A binary operator-style calling form e.g. receiver attribute argument
. There is a limit that only one (and not zero) positional argument can be applied for binary operator form and its function is an attribute form.
The mostly used calling form is a normal calling form. Because it can replace any other two types of calling forms.
The purpose of a simple calling form is a typography of C block-style macro, fexpr or higher-order functions:
[1, 2, 3, 4].for-each!: \(el) {
io.*stdout*.write(el)
}
All of binary operator forms can be replaced by any other calling forms. So, every calling form of the next code has the same behavior:
receiver method argument
receiver.method: argument
receiver.method(argument)
The purpose of a binary operator form is a typography of binary operator-style methods as its name. For example, sum two integers can operated with the instance method +
of the <integer>
class like 12.+(34)
, it usually is represented like the following code:
12 + 34
All of simple calling forms can be replaced by normal calling forms:
function: argument
function(argument)
Calling a function with no arguments or some named arguments can be represented by normal calling form only:
function()
function(a: 1)
function(a, b: 2)
function(a: 1, b: 2)
Calling form type | Accepted arguments types | Minimum–maximum arguments |
---|---|---|
Normal calling form | Positional, Named | 0–(no maximum limit) |
Simple calling form | Positional | 1–(no maximum limit) |
Binary operator form | Positional | 1–1 |
It is not every object can be called. There are <callable>
objects like functions, macros, fexprs and classes. <callable>
type has a method named +call
, and it can say that calling forms are aliases of invoking a method +call
.
Therefore, every form of the following code shares the same behavior:
f(x)
f.+call(x)
f.+call.+call(x)
f.+call.+call.+call(x)
Because all of +call
attributes (in <callable>
objects) have to be a <callable>
object also. Recursive invoking +call
method can be stopped when a +call
attribute and its receiver are the same object.
Calling an object that is not <callable>
causes a <call-error>
, a subtype of <type-error>
.
Any trial of definition of call causes a <call-definition-error>
, a subtype of <call-error>
and <definition-error>
unless +define
macro is redefined by user.
<call> ::= <form> "(" <argument-list> ")"
| <form> ":" <unnamed-argument-list>
| <binary-operator>
<binary-operator> ::= <form> <symbol> <form>
An index is an argument list wrapped with square brackets. Its purpose is to style accessor methods of data structures typographically similar to subscript indexing e.g. map[:key]
.
It is a syntactic sugar for a call +index
macro (or fexpr), assumed already defined. For example, the following two forms share the same behavior:
[1, 2, 3, 4]
+index (1, 2, 3, 4)
In short, an index form:
[argument-list]
is an alias of:
+index (argument-list)
And of course, an index form with no arguments:
[]
is an alias of:
+index ()
Index can be an attribute name also. In this case, it finds +index
macro (or fexpr) in the same receiver.
For example, two forms of the following code have the same behavior:
list.[1]
list.+index (1)
In short, a following form:
receiver.[argument-list]
is an alias of:
receiver.+index (argument-list)
And a middle period character (.
) can be omitted when an index is an attribute name:
receiver[argument-list]
The best examples of typographical use of indices are list comprehensions and random access for <sequence>
s.
[x * 3, x: 1 ~ 100, x +isa? <odd-number>]
list[123]
Environments or receivers that invoke indices have to be an instance of <indexable>
. If it’s not, it raises a <index-error>
, a subtype of <type-error>
.
Unless +define
macro is redefined by user, the following two forms are equivalent:
receiver[index] := value
receiver.+define-index (index, value, local: true)
In the same manner, setf definition form:
receiver[index] = value
is the same as:
receiver.+define-index (index, value, local: false)
provided the receiver
is an <index-definable>
object. If not, it raises <index-definition-error>
, a subtype of <index-error>
and <definition-error>
.
Note that a value +define-index
method returns is just ignored and an index definition form returns its rvalue, evaluated. So if we have to explain about index definitions exactly, f(r[i] := v())
is equivalent to _v := v(); r.+define-index (i, _v, local: true); f(_v)
(but the name _v
is never exposed, is used only for explanation).
<index> ::= "[" <argument-list> "]"
A definition is either a call to a user-defined defining macro (or fexpr), or a call to a built-in defining macro. In any case, it is a just syntactic sugar for a call +define
macro, assumed already defined.
For example, following two lines have the same behavior:
minhee-hong := <person>()
+define (minhee-hong, <person>(), local: true)
Following code is the same:
greet(name: <string>) = "Hi, " ++ name
+define (greet(name: <string>), "Hi, " ++ name, local: false)
In short, a :=
local definition form (:=
, also called as let definition):
(lvalue-form) := (rvalue-form)
is an alias of:
+define ((lvalue-form), (rvalue-form), local: true)
In the same manner, a setf definition form (=
, also called as assignment):
(lvalue-from) = (rvalue-from)
is an alias of:
+define ((lvalue-form), (rvalue-form), local: false)
There’s +define
defined in the root class <object>
, a built-in defining macro. So typically a definition makes a binding in the environment by default.
<definition> ::= <form> ( ":=" | "=" ) <form>
The main purpose of blocks is to style macros/fexprs typographically to make programmers to guess that forms in the block are not evaluated immediately. It takes a program that contains zero or more forms.
It is a syntactic sugar for a call +block
macro (or fexpr), assumed already defined.
{ msg := "log: " ++ $0
io.*stdout*.write(msg) }
For example, the above code and the following code share the same behavior:
+block (msg := "log: " ++ $0,
io.*stdout*.write(msg))
In short, a block form:
{ form-1; form-2; form-3; form-n }
is an alias of:
+block (form-1, form-2, form-3, form-n)
And of course, an empty block form:
{}
is an alias of:
+block ()
Block can be an attribute name also. In this case, it finds +block
macro (or fexpr) in the same receiver.
For example, following forms of two lines have the same behavior:
\(a, b).{ a + b }
\(a, b).+block (a + b)
In short, a following form:
receiver.{ forms }
is an alias of:
receiver.+block (forms)
And a middle period character (.
) can be omitted when a block is an attribute name:
receiver { forms }
The best example of typographical use of blocks is a lambda syntax.
\(a, b) { a + b }
Environments or receivers that invoke blocks have to be an instance of <block-callable>
. If it’s not, it raises a <block-error>
, a subtype of <type-error>
.
Any trial of definition of block or block attribute causes a <block-definition-error>
, a subtype of <block-error>
and <definition-error>
unless +define
macro is redefined by user.
Under the hood, a form receiver { forms }
actually becomes compiled to:
receiver.+get-attribute(:({ forms }))
Because { forms }
is an attribute name in this case. And then, default +get-attribute
method defined in <object>
raises a <block-error>
when its name
argument is a <block>
. Instead, <block-callable>
overrides +get-attribute
method to call +block
method.
<block> ::= "{" <program> "}"
Argument lists are mini syntax used inside calls or indices. Arguments are separated by comma characters (,
). A trailing comma after all arguments is also allowed, but usually omitted.
There are two argument types:
Passed by its position.
Passed by its name. There is a preceding name symbol with a trailing colon (:
) that separates an argument name and its value for each named argument e.g. name: value
.
There is a defined rule of argument passing for function applications and most macro expansions (but except fexpr calls).
For example, assume that there’s a defined function f
:
f := \(a, b, c, d) { [a, b, c, d] }
Then four applications of the following code share the same return value [1, 2, 3, 4]
:
f(1, 2, 3, 4)
f(a: 1, b: 2, c: 3, d: 4)
f(d: 4, c: 3, 1, 2)
f(2, a: 1, d: 4, 3)
But rarely, fexprs can have a their different rule of arguments passing. For a representative instance, list comprehensions are not a specific syntax of Veeyu, but implemented by a fexpr aware to its exact argument positions and names.
<argument-list> ::= [ <argument> { "," <argument> } [ "," ] ]
<unnamed-argument-list> ::= [ <form> { "," <form> } [ "," ] ]
<argument> ::= [ <symbol> ":" ] <form>
Runtime library defined this section is core of Veeyu. All names are defined under veeyu
module, imported automatically.
An instance of <abstract-class>
; cannot be instantiated directly and would be subclassed.
An instance of <class>
.
<c>
An object that has <c>
as its +class
property.
<t>
An object
that satisfies <t>.is-type?(object)
returns true
i.e. object.isa?(<t>)
returns true
.
A property, an instance of <method>
.
An instance of <type>
.
An instance of <union-type>
.
An instance of <intersection-type>
.
\
Actually there’s no special syntax for defining functions. Instead, Veeyu provides \
macro defined in the standard runtime library. You can define functions like the following code:
factorial := \(integer) {
(integer > 1) then (factorial(integer - 1) * integer, 1)
}
It supports lexical scoping as well.
adder := \(number) {
\(delta) {
number = number + delta
}
}
The number
variable is not a local variable of the inner function, but the outer function (adder
).
>>> a := adder(1); a(1)
==> 2
>>> a(2)
==> 4
>>> b := adder(1); b(1)
==> 2
Note that =
operator is used instead of :=
. When :=
is used, number
always becomes defined again as local variable.
>>> adder2 := \(number) {
... \(delta) { number := number + delta }
... }
==> \(number) { ... }
>>> c := adder2(1); c(1)
==> 2
>>> c(1)
==> 2
>>> c(2)
==> 3
assert
The assert
macro is a way to insert debugging assertions into a program.
a := 1
b := 2
assert (b == (a + 1))
The above code becomes compiled into (with debug mode):
a := 1
b := 2
(b == (a + 1)).then(<assert-error>().rise!(), nil)
It takes an optional second argument, the message string of <assert-error>
, also.
assert (b == (a + 1), "b must be a + 1")
The above code is compiled to (with debug mode):
(b == (a + 1)).then(<assert-error("b must be a + 1").rise!(), nil)
The assert
macro is ignored when debug mode is turned off. Every assert
call is compiled into empty expression. So, the efficiency of compiled program without debug mode is equivalent to the same program without assert
calls.
<boolean>
, a subclass of <function>
<boolean>
is the type of the values false
and true
for logical operations. There are only two instances of <boolean>
class: the true
and the false
. And it cannot be subclassed.
not
Logical complement. The negated logical value.
The true
when the boolean value is the false
, otherwise the false
.
The below code is its reference implementation:
true.not := false
false.not := true
+apply
returns <object>
Returns a given true-value
when this is the true
, and returns a given false-value
when this is th false
.
true-value
A value that is going to be returned when the boolean value is the true
.
false-value
A value that is going to be returned when the boolean value is the false
.
The below code is its reference implementation:
true.+apply := \(true-value, false-value) {
true-value
}
false.+apply := \(true-value, false-value) {
false-value
}
then
returns <object>
Evaluates true-value
and returns its evaluation result if this boolean value is the true
. (false-value
is not evaluated in this case.)
Evaluates false-value
and returns its evaluation result if this boolean value is the false
. (true-value
is not evaluated in this case.)
The key difference between this and +apply
(of <boolean>
) is that the former evaluates only one of its arguments, but but latter evaluates all of its arguments.
true-value
A value that is going to be returned when the boolean value is the true
.
false-value
A value that is going to be returned when the boolean value is the false
.
The following code is the reference implementation:
true.then := macro (true-value, false-value) {
true-value(environment)
}
false.then := macro (true-value, false-value) {
false-value(environment)
}
and
returns <boolean>
Logical conjunction.
Always returns the false
without any evaluation of the operand
when this boolean value is the false
, otherwise evaluates the operand
then tests truth value of its evaluation result.
Returns the false
if the evaluation result of the given operand
is considered false.
Returns the true
when this boolean value and the evaluation result of the operand
are both considered true.
operand
The below code is its reference implementation:
true.and := macro (operand) {
<boolean>(operand(environment))
}
false.and := macro (operand) {
false
}
or
returns <boolean>
Logical disjunction.
Always returns the true
with no evaluation of the given operand
when this boolean value is the true
, otherwise evaluates the operand
then tests truth value of its evaluation result.
Returns the true
if the evaluation result of the given operand
is considered true.
Returns the false
when this boolean value and the evaluation result of the operand
are both considered false.
operand
The below code is its reference implementation:
true.or := macro (operand) {
true
}
false.or := macro (operand) {
<boolean>(operand(environment))
}
and?
returns <boolean>
Logical conjunction with applicative order.
Always returns the false
when this boolean value is the false
. Otherwise, returns operand
.
The key difference between this and macro and
(of <boolean>
) is that the former always evaluates operand
, but the latter evaluates operand
only when this boolean value is true
.
operand
The below code is its reference implementation:
true.and? := \(operand) {
operand
}
false.and? := \(operand) {
false
}
or?
returns <boolean>
Logical disjunction with applicative order.
Always returns the true
when this boolean value is the true
. Otherwise, returns operand
.
The key difference between this and macro or
(of <boolean>
) is that the former always evaluates operand
, but the latter evaluates operand
only when this boolean value is false
.
operand
The below code is its reference implementation:
true.or? := \(operand) {
true
}
false.or? := \(operand) {
operand
}
<number>
It is an abstract root class for several number types e.g. <complex>
, <real>
, <rational>
, <integral>
.
<complex>
, a subclas of <number>
Complex numbers have a real
and imaginary
part, which are each a <real>
number.
+
returns <complex>
Adds this number and operand
and then returns the result of addition. This operation has to be commutative and associative.
operand
(<complex>
)-
returns <complex>
Subtracts subtrahend
from this number and returns the result it has.
subtrahend
(<complex>
)*
returns <complex>
Multiplies this number by operand
and then returns the result of multiplication. This operation has to be commutative, associative and distributive.
operand
(<complex>
)\
returns <complex>
Divides this number into divisor
and then returns its quotient.
divisor
(<complex>
)real
is a <real>
The real part of this complex number.
imaginary
is a <real>
The imaginary part of this complex number.
conjugate
returns <complex>
Returns the conjugation.
<real>
, a subclass of <complex>
A real number value.
ceil
returns <integer>
Returns the ceiling of this number, the smallest integer greater than or equal to this number.
floor
returns <integer>
Returns the floor of this number, the largest integer less than or equal to this number.
<string>
, a subclass of <sequence>
It is a data structure for storing and manipulating a string. Implementations can choose the right algorithms for their jobs e.g. ropes or ordinary array-based strings.
It has to be immutable and doesn’t have any operations that manipulate string itself in-place. We recommend to implement this as rope for such reason.
replace
returns <string>
Returns a new <string>
of it with all occurrences of substring old
replaced by new
.
old
(<string>
)The old string to be replaced by new
.
new
(<string>
)The new string to replace with old
.
trim-first
returns <string>
Returns a new <string>
of it with leading whitespace characters removed.
trim-last
returns <string>
Returns a new <string>
of it with trailing whitespace characters removed.
trim
returns <string>
Returns a new <string>
of it with trailing and trailing whitespace characters removed.
Veeyu has modern and powerful object-oriented runtime type facilities: metaclasses, multiple inheritance (using C3 linearization) and multimethods.
<object>
The <object>
is the root of the class hierarchy. Every class has <object>
as a superclass at least. All objects implement the methods of this class.
By default, every object is immutable; their attributes cannot be changed after instantiated once.
+make
returns <object>
Creates a new instance of the class with attributes that are given by keyword arguments. And returns a created new instance.
For example, <object>(a: 1, b: 2)
calls the following code internally:
<object>.+make(a: 1, b: 2)
+class
The <class>
instance what class the object has instantiated from.
Example | Evaluated value |
---|---|
"str".+class |
<string> |
c'a'.+class |
<character> |
:sym.+class |
<identifier> |
:(a.b).+class |
<attribute> |
+isa?
returns <boolean>
Returns true
when the object is a kind of a given type
. It exactly equals to type.is-type?(object)
when type
is a <type>
instance and object
is the object.
type
(<type>
)+hash
The integer that equals for equal objects even if objects have identically different references. This number has a enough precision in instances of the same class. There’s no defined exact rule to generate this number and it depends on implementation details. By default, its value equals to the instance’s +id
value, but it can be overridden on subclasses.
This property is used for finding the equality of an object.
+get-attribute
returns <object>
Returns a value of the attribute name
.
name
(<attribute-name>
)For example, two variables named hash
and hash2
in the following code have the same value:
hash := an-object.+get-attribute(:+hash)
hash2 := an-object.+hash
&&
returns <object>
Returns this value when it is considered false in boolean context without any evaluation of operand
.
Otherwise, evaluates operand
and then returns its evaluation result.
operand
||
returns <object>
Returns this value when it is considered true in boolean context with no evaluation of operand
.
Otherwise, evaluates operand
and then returns its evaluation result.
operand
<type>
The abstract base class for Veeyu’s runtime type system. It makes the type protocol of Veeyu extensible by user.
is-type?
returns <boolean>
It is a method for implementing +isa?
method of an every object. Returns true
when a given object
is a kind of the type.
object
|
returns <union-type>
Returns the type which represents the disjunction of given operand types respectively.
<integer-or-string> := <integer> | <string>
operand
(<type>
)<union-type>
with.&
returns <intersection-type>
Returns the type which represents the conjunction of given operand types respectively.
<mutable-enumerable> := <mutable-object> & <enumerable>
operand
(<type>
)<intersection-type>
with.<union-type>
, a subclass of <type>
, <type-set>
The type which represents the disjunction of types in the set respectively.
<integer-or-string> := <union-type>(<integer>, <string>)
<intersection-type>
, a subclass of <type>
, <type-set>
The type which represents the conjunction of types in the set respectively.
<mutable-enumerable> := <intersection-type>(<mutable-object>, <enumerable>)
<predicate-type>
, a subclass of <type>
It adapts a predicate function to <type>
interface.
<odd-number> := <predicate-type>: \(value: <integer>) {
value.rem(2) == 1
}
predicate
(<function>
)<boolean>
result.<abstract-class>
, a subclass of <type>
The class for abstract classes. Its every instance cannot create new instances of that.
is-type?
returns <boolean>
Returns true
when object.+class
is this class or a subclass of this class.
object
subclass
returns <class>
Makes a subclass the (abstract) class, and then returns the made subclass.
<class>
, a subclass of <abstract-class>
, <function>
The construct that is used as a template to create instances of that class. Classes are values as well, and these are instance of <class>
class. So creating a class is just making an instance of <class>
:
<point> := <class>(x: <integer>,
y: <integer>)
In the above example, <point>
class has two attributes: x
and y
. In order to instantiate a class, just call it:
p := <point>(x: 10, y: 15)
Every <abstract-class>
object has the subclass
method for inheritance:
<point3d> := <point>.subclass(z: <integer>)
p3d := <point3d>(p, z: 20)
There’s a more flexible way to define attributes and methods as well:
<point> := <class> {
+make := class-method (x: <integer>, y: <integer>) {
super.+make(x: x, y: y)
}
is-aligned-horizontally? := method (point: <point>) {
self.y == point.y
}
is-aligned-vertically? := method (point: <point>) {
self.x == point.x
}
}
+apply
returns <object>
Creates a new instance of this class and returns it. Exactly it is a just proxy method to +make
user-defined per-instance method: all arguments of this method are transparently passed to +make
method and a value +make
method application returns is returned straight.
<descriptor>
Every class (including abstract classes) has their own prototype
, and its instance finds an attribute from itself to prototype
of its class.
When the attribute dispatcher (+get-attribute
) found an attribute value from prototype
of the class and it is a <descriptor>
, one more procedure has injected: .
get
returns <object>
Called to get the attribute of the receiver
.
receiver
<mutable-object>
Veeyu objects are immutable and stateless by default, but it is a special class for stateful objects. Subclassing <mutable-object>
makes attributes of instances able to be changed.
+initialize!
returns <nil>
The constructor method of nondeterministic version that is called after a instance has instantiated.
A returned value of this method is just ignored silently.
+id
The identical but arbitrary integer for the same reference’s object. This value is unique for respective objects. This specification doesn’t define any detail approach to generate this number and it’s just implementation details. The typical way is to use memory addresses of objects.
This property is used for finding the identity of an object.
<mutable-descriptor>
, a subclass of <descriptor>
Descriptor interface for <mutable-object>
. It defines two more abstract methods in addition to abstract method get
of abstract class <descriptor>
for defining and undefining an attribute.
define
returns <object>
Call to define the attribute on the receiver
as the value
.
receiver
The receiver object of the attribute.
value
The new attribute value to set.
undefine
Called to undefine the attribute on the receiver
.
receiver
Veeyu is a homoiconic language like Lisp. Therefore it has to provide classes for representing its codes. There are several types of forms in Veeyu. Form classes are straight model classes of them. An instance of these classes can be created with a quote syntax also, not only by these constructors.
quote
returns <form>
Quotes a given form
.
What it does basically is equivalent to a quote syntax, but the main difference is a form interpolation. In this macro, ~
symbol is interpreted as an unquote to interpolate a form. For example,
f := :y
quote(x + ~f + x.~f)
is evaluated to:
:(x + y + x.y)
In order to escape ~
to express ~
as just a symbol, not an unquote, use ~~
instead. In the same manner, in order to express ~~
, use ~~~
. For example,
f := :x
quote(f + ~f + ~~f + ~~~f + ~~~~f)
is evaluated to:
:(f + x + ~f + ~~f + ~~~f)
form
<node>
The abstract base class of AST nodes e.g. <form>
, <symbol>
, <program>
.
<form>
, a subclass of <function>
, <node>
Every form class is its subclass.
quote
returns <quote>
Returns an instance of <quote>
, of this form.
+apply
returns <object>
Evaluates this form in the environment
then returns its evaluation result.
environment
replace
returns <form>
Replaces all forms that equals to old
of in the entire form tree with new
form, then returns its resulted form.
For example,
:(a + b + (c - d)).replace(:c, :k)
returns:
:(a + b + (k - d))
There’s no limit of form type, so:
:(a + b + (c - d(e - f) * g)).replace(:(d(e - f)), :h)
returns:
:(a + b + (c - h * g))
By default, it doesn’t find old
forms in the quoted forms. For example,
:(a + :(b + c)).replace(:b, :d)
returns just:
:(a + :(b + c))
Because there’s b
, but it is quoted. In order to dig quoted forms also, turn the including-quote
option on.
:(a + :(b + c)).replace(:b, :d, includng-quote: true)
will return the form you expected:
:(a + :(d + c))
old
(<form>
, <function>
)A form to be replaced by new
form.
new
(<form>
, <function>
)A form to replace old
forms.
including-quote
(<boolean>
)Whether it digs <quote>
forms also. Default is false
.
<quote>
, a subclass of <form>
+make
returns <quote>
Returns a <quote>
instance that quotes the form
. It is the same as form.quote()
.
form
(<form>
)form
The quoted <form>
instance.
<attribute-name>
, a subclass of <form>
The abstract base class of forms that can be used as <attribute>
forms’ name such as <symbol>
, <index>
or <block>
.
<symbol>
, a subclass of <attribute-name>
Symbol forms.
>>> :hello.+class
<symbol>
+make
returns <symbol>
Gets a symbol.
symbol
(<symbol> | <string>
)<type-error>
.<index>
, a subclass of <attribute-name>
Index forms.
>>> :([123]).+class
<index>
<block>
, a subclass of <attribute-name>
Block forms.
>>> :({ block! }).+class
<block>
+make
returns <block>
Creates a <block>
form.
program
(<sequence> of <form>
)program
is a <program>
The program that the block contains.
<attribute>
, a subclass of <form>
+make
returns <attribute>
Creates an <attribute>
form object.
>>> <attribute>(:receiver, :attribute)
:(receiver.attribute)
>>> <attribute>(:receiver, :([123]))
:(receiver[123])
>>> <attribute>(:receiver, :({ block! }))
:(receiver { block! })
receiver
(<form>
)The receiver form.
attribute
(<attribute-name>
)The attribute name.
receiver
is a <form>
The form that is thae receiver of the message.
attribute
is a <attribute-name>
The attribute name.
<program>
, a subclass of <node>
, <sequence>
A list of forms.
+make
returns <program>
Creates a <program>
node.
forms
(<sequence> of <form>
)Veeyu provides exception handling mechanism and basic hierarchy of exceptions and errors.
<signal>
The abstract base class for all built-in exceptions.
rise!
returns <nil>
Terminates current running function call and raises itself up to call stack until it has caught.
<exception>
, a subclass of <signal>
All exceptions subclass this class except <signal>
s to control language.
<error>
, a subclass of <exception>
All errorss subclass this class.
<return>
, a subclass of <signal>
In Veeyu internals, value returning of function is implemented by raising <return>
signal exception with a value to return. For example,
f := \() {
<return>(value: 123).rise!()
}
val := f()
is equivalent to the following code:
f := \() {
return (123)
}
val := f()
In other words, in the local environment of function body, return
function is implemented like:
return := \(value) {
<return>(value).rise!()
}
+make
returns <return>
Creates a <return>
instance with the value
to return.
value
value
The value a function returns.
raise
Just raises an exception
. It is equivalent to exception.rise!()
except it also takes any subclass of <signal>
instead of instance of <signal>
. Therefore, the following two lines have the same behavior:
raise: <exception>()
raise (<exception>)
Its return value is so meaningless that it’s undefined.
exception
(<signal>
)Veeyu contains several common data structures like strings, hash maps. You can find test codes for them in the directory /runtime/tests/data-structures/
.
<container>
It is a type abstracts the most common concepts of data structures: contains?
and add
. Every instance of <container>
have to satisfy that c.add(e) contains? e
where c
is an instance of <container>
and e
is an instance of a type c
accepts as its element. It assumes that the container is immutable by default.
contains?
returns <boolean>
Returns true
only when the container contains an element
. This method is deterministic.
element
add
returns <container>
Returns a new <container>
instance contains the element
. This method is deterministic and never manipulates the container itself.
element
<enumerable>
, a subclass of <container>
It is a type abstracts the common concepts of traversable <container>
s: first
, rest
and concatenation(++
).
null
A <boolean>
value whether the container is empty. It is false
if it has any one or more elements. Otherwise, it is true
.
first
The first element of the enumerable container. This property is deterministic. It is nil
when the container is empty.
rest
A <enumerable>
container that contains all elements of the original container except its first element.
++
returns <enumerable>
Returns a new <enumerable>
instance concatenates a container
after the original container.
container
(<enumerable>
)fold
returns <object>
Aggregates values in the enumerable container then returns the aggregated value. The order of values to be aggregated is not deterministic. For instance, this method can be run in parallel internally, for efficiency. Therefore, binary-operation
should satisfy that binary-operation(v1, v2) == binary-operation(v2, v1)
(commutative law) and binary-operation(v1, binary-operation(v2, v3)) == binary-operation(binary-operation(v1, v2), v3)
(associativity).
If you look for the similar method that guarantee the left-to-right order of operations, find fold-left
method of the <sequence>
class.
For example, the following code calculates the sum of l
, assumed that it is an <enumerable>
and contains integers only:
l.fold(\(a, b) { a + b }, 0)
For another simpler example, the following code calculates the cardinality of l
, assumed <enumerable>
:
l.fold(\(a, b) { a + 1 }, 0)
binary-operation
(<function>
)A function which takes two arguments. It should satisfy commutative law and associativity both.
identity
An identity element for the binary-operation
. If the enumerable container is empty, this method returns the identity
as it is.
The following code is the reference implementation:
method (binary-operation: <function> taking 2, identity) {
empty := nil +is? self.rest
empty.then(identity,
binary-operation(identity,
self.rest.fold(binary-operation, self.first)))
}
is-all?
Tests whether its all elements are true
. It optionally takes an argument map
as well.
map
(<function>
)<boolean>
value. Default is <boolean>
.The next code is the reference implementation:
method (map(<function> taking 1) := <boolean>) {
self.null.then(true,
map(self.first) and self.rest.is-all?(map))
}
is-any?
Tests whether one or more elements are true
. It optionally takes an argument map
as well.
map
(<function>
)<boolean>
value. Default is <boolean>
.The next code is the reference implementation:
method (map(<function> taking 1) := <boolean>) {
self.null.then(false,
map(self.first) or self.rest.is-any?(map))
}
each!
A non-deterministic method that iterates through its elements and apply each element to do!
function.
do!
(<function>
)The next code is the reference implementation:
method (do!: <function> taking 1) {
empty := nil +is? self.rest
empty({},
{ do!(self.first)
self.rest.for-each!(do!) })()
}
<sequence>
, a subclass of <enumerable>
,<indexable>
It is a type abstracts the common concepts of randomly accessible sequential <container>
s.
fold-left
returns <object>
This method does an operation similar to the fold
method of <enumerable>
class, but it guarantee that its elements are joined from left to right. Therefore, the binary-operation
have not to satisfy commutative law or associativity either.
For exmaple, the next code returns 85
:
"""It calculates `100 - 1 - 2 - 3 - 4 - 5`."""
[1, 2, 3, 4, 5].fold(\(a, b) { a - b }, 100)
binary-operation
(<function>
)A function which takes two arguments.
identity
An left identity element for the binary-operation
. If the enumerable container is empty, this method returns the identity
as it is.
The following code is the reference implementation:
method (binary-operation: <function> taking 2, identity) {
empty := nil +is? self.rest
empty.then(identity,
binary-operation(identity,
self.rest.fold-left(binary-operation,
self.first)))
}
<map>
, a subclass of <container>
, <indexable>
It is a type abstracts the common concepts of maps, also called dictionaries, e.g. hash maps, associative arrays.
Veeyu provides facilities of modules, packages and namespaces to support modular programming.
Similar to other popular programming languages like Java or Python, Veeyu’s module layout is designed in consideration of filesystem-based packaging. It’s slightly simple: the Veeyu file suffixed .vu
becomes a module of the same name (a suffix .vu
goes, of course).
For example, starman.vu
becomes a module named just starman
.
This rule should be implemented in most of, filesystem-based platform, but if the platform is not filesystem-based, an implementation can consider alternative layouts that is not based on filesystem.
require
require
is a built-in macro that imports modules. Its syntax is:
require: module-name
The above imports a module named module-name
. It takes one or more arguments and imports specified modules. For example, the following code imports two modules named datetime
and regex
:
require: datetime, regex
It’s possible to import only an attribute including submodules in a module. For example, the following code imports a module bytes.encodings
, becomes named as just encodings
, and an object io.*stdout*
, also becomes named as just *stdout*
:
require: bytes.encodings,
io.*stdout*
So you can use bytes.encodings
by the name encodings
instead of bytes.encodings
, and io.*stdout*
also can be used by the name *stdout*
in the same manner:
message := 'Hello modular programming!'
bytes := encodings.<utf-8>.encode(message)
*stdout*.write-line!(bytes)
Note that it defines no symbols like bytes
or io
— because its main purpose is to avoid namespace pollution.
Also you can name module to be imported by yourself — use definition form:
require: encs := bytes.encodings,
stdout := io.*stdout*
import
returns <object>
import
is a function lower than require
(which can be implemented by using import
function). It takes a module name (in <symbol>
) then returns the imported module object.
For example:
require: bytes.encodings
The above code using require
macro can be replaced with the above equivalent code using import
function:
encodings := import(:(bytes.encodings))
module-name
(<symbol>
)Like other many things of Veeyu, modules also are first-class objects. Technically, each module is an instance of <module>
class, so you can instantiate a module by hand:
hello := <module>()
Note that <module>
is not a <mutable-object>
, so you cannot define any functions or classes if it has instantiated once. In order to make a module to imply functions or classes you define, put an <environment>
object, has functions and classes, when you initialize a <module>
.
hello-env := <environment>()
with (hello-env) {
greet := \(name) {
'Hello ' ++ name
}
}
hello := <module>(hello-env)
Then, use the module named hello
.
>>> hello.greet('Hong Minhee')
==> 'Hong Minhee'
use-literal
Special-purpose importing macro. It makes the source able to use third party defined literals. For example:
use-literal: bytes (B)
example := B'byte string'
indicates the rest of the source code can use B
-prefixed string literals like B'byte string'
.
Under the hood, the above example code is equivalent to the following code:
require: io.bytes.+literal-B-type, io.bytes.+literal-B
You can use alternative prefix character also:
use-literal: bytes (B: o)
example := o'byte string'
Veeyu provides several standard libraries like input/output facilities, text processing tools, concurrent programming tools, network programming libraries, etc by default. It’s Veeyu’s “included batteries.”
bytes
— Byte StringsIt provides byte type and bidrectional codec interface between bytes and string.
B
This module defines prefix character B
for byte string literal.
use-literal: bytes (B)
example := B'byte string'
Byte string literals are evaluated as a value kind of <sequence> of bytes.<byte>
.
bytes.<byte>
The byte character.
bytes.<encoding>
It is an abstract base class for string—bytes codecs.
bytes.encodings
— Codecsbytes.encodings.<ascii>
, a subclass of bytes.<encoding>
ASCII encoding implementation.
bytes.encodings.<latin-1>
, a subclass of bytes.<encoding>
ISO/IEC 8859–1, also known as Latin–1, encoding implementation.
bytes.encodings.<utf-7>
, a subclass of bytes.<encoding>
UTF–7 encoding implementation.
bytes.encodings.<utf-8>
, a subclass of bytes.<encoding>
UTF–8 encoding implementation.
bytes.encodings.<utf-16>
, a subclass of bytes.<encoding>
TODO
bytes.encodings.<utf-16-be>
, a subclass ofbytes.encodings.<utf-16>
TODO
bytes.encodings.<utf-16-le>
, a subclass ofbytes.encodings.<utf-16>
TODO
bytes.encodings.<utf-32>
, a subclass of bytes.<encoding>
TODO
bytes.encodings.<utf-32-be>
, a subclass ofbytes.encodings.<utf-32>
TODO
bytes.encodings.<utf-32-le>
, a subclass ofbytes.encodings.<utf-32>
TODO
io
— Input/Output StreamsTo make programs useful, these have to interact with “outer world” e.g. users, other programs. This module abstracts generalized stream concept from channels for such communications.
io.<input-stream>
, a subclass of<mutable-object>
io.<input>
is a basic interface class for input stream. It forces subclasses to implement instance methods read!
and read-line!
.
read!
returns <sequence> of bytes.<byte>
Reads size
bytes from the buffer and returns it. It returns an empty byte string when there’s no remaining bytes to read.
size
(<unsigned-integer>
)read-line!
returns <sqeuence> of bytes.<byte>
Reads a line from the buffer and returns it. The result includes trailing new line character(s) e.g. "\n"
, "\r\n"
. It returns an empty byte string when there’s no remaining bytes to read.
io.<output-stream>
, a subclass of<mutable-object>
io.<output-stream>
is a basic interface class for ouput stream. It forces subclasses to implement an instance method write!
.
write!
returns <nil>
Writes given bytes
to the buffer.
bytes
(<sequence> of bytes.<byte>
)write-line!
returns <nil>
Similar to write!
method except it also writes trailing new line character and flush as well.
bytes
(<sequence> of bytes.<byte>
)