|
|---|
Q is a new programming language designed and implemented by Q Software Solutions.
Q combines the best of object-orientation with ideas from functional programming to produce a language well-suited for the engineering of precision software for large and small systems.
Several existing languages came close to meeting our requirements, but none of them was exactly right. It was not possible to extend one of these languages to meet our needs - a good language is defined not just by what it provides, but by what it excludes.
The syntax of Q closely resembles that of Eiffel. Many of the ideas embodied in Q were drawn from Eiffel, Haskell and Python. Almost as many came from a multitude of niche languages that we studied. Other ideas were the result of our own in-house design work.
Our aim was and is to keep the language as small and consistent as possible. We based the design of Q on a few basic principles:
Design By ContractCommand-Query SeparationWithout a clear specification of what a software component should do, we cannot hope to re-use it successfully in large, mission-critical applications.
Bertrand Meyer's Design By Contract allows each routine to be specified by its preconditions and postconditions, each loop to be specified by its variant and invariant, each class to be specified by its class invariant, and complex code fragments to be specified by checks.
Our extensive experience of Design By Contract in Eiffel has convinced us of its value to the software development process. In Q, we go beyond Eiffel's Design By Contract by providing for the runtime checking of quantifiers (e.g. "for each" and "for some").
Programming-in-the-small based on functionsImagine what the world would be like if things could change just by looking at them!
Yet many programminlanguages allow just that - for a query on the state of an object to change the state of that object.
Q enforces command-query separation - to enhance the readability of reliability of the code. The syntax and semantics of Q have been designed so that command-query separation is both elegant and efficient.
Programming-in-the-large based on objectsPure functional programming enables us to reason about the behaviour of our software. It provides the most straightforward way to formally reason about the correctness of our applications.
Pure functions are used in Q for programming-in-the-small. The low-level data types (Integers, Characters, etc) are purely functional, or applicative. User-written classes can just as easily be made purely functional.
Object-orientation offers a powerful basis for managing complexity in very large applications. It allows for a natural implementation of the user interface. It is also well-suited to persistent storage, and certain performance-bound applications.
Q fully supports object-orientation with multiple inheritance, polymorphism, and constrained genericity.
Q allows the programmer to move freely between the functional and object-oriented paradigms. This often requires no more than the insertion of a single keyword.
We have taken care to integrate numerics into the framework of Q. Extension classes allow a new numeric type
to "slot in" to the existing class hierarchy.
Enumerated types are cleanly supported in Q.
Q features automatic garbage collection. The programmer need not be concerned with storage management or reference counting.
External software written in C can be seamlessly mixed with Q code, allowing easy integration of third-party C libraries.
The low-level Q types merge seamlessly into their implementation as a sequence of bits. This allows a programmer to develop at any level from system software to fully-abstract data types.
The Q toolset includes a pretty-printer, a short tool (to extract a class interface) and a flat tool (to flatten the inherited features of a class into the class text).
class Hello
creation make
do
print("Hello World!" \n)
end
endclass
To run this, put it in a file hello.q and type:
q hello
At first an example on how to interface with C
class Unix_file_system_commands
is
File_common
public -- file and Directory manipulations
-- questions is if error messages are helpful while using this
in programs
-- maybe another higher layer should cope with those conditions
2000-07-20 (FD)
-- probably with exception handling this error message can be
changed to some
-- more useful usage
make_hard_link(from: Mutable_string; to: Mutable_string)
do
reset_error_code;
if file_info.exists_file(from) then
if not file_info.exists_file(to) then
c_make_hard_link(from, to);
else
error.print_string("File ")
error.print_mutable_string(to);
error.print_string(" does exist, can not make
link.");
endif;
else
error.print_string("File ")
error.print_mutable_string(from);
error.print_string(" does not exist, can not make
link.");
endif
end
make_symbolic_link(from: Mutable_string; to: Mutable_string)
do
reset_error_code;
if file_info.exists_file(from) then
if not file_info.exists_file(to) then
c_make_symbolic_link(from, to);
else
error.print_string("File ")
error.print_mutable_string(to);
error.print_string(" does exist, can not make
link.");
endif;
else
error.print_string("File ")
error.print_mutable_string(from);
error.print_string(" does not exist, can not make
link.");
endif
end
unlink(pathname: Mutable_string)
do
reset_error_code
if file_info.exists_file(pathname) then
c_unlink(pathname);
else
error.print_string("File ")
error.print_mutable_string(pathname);
error.print_string(" does not exist, can not unlink");
endif
end
remove_file(pathname: Mutable_string)
do
unlink(pathname)
end
change_name(from: Mutable_string; to: Mutable_string)
-- rename unconditionally, if to exists and permissions allow
-- all will gone
do
reset_error_code;
if file_info.exists_file(from) then
c_rename(from, to);
else
error.print_string("File ")
error.print_mutable_string(from);
error.print_string(" does not exist, can not rename");
endif
end
public -- directory manipulation
make_directory(pathname: Mutable_string)
do
reset_error_code;
if file_info.exists_file(pathname) then
error.print_mutable_string(pathname);
error.print_string(" exists, cannot make a new
directory");
else
c_make_directory(pathname);
endif
end
remove_directory(pathname: Mutable_string)
require
is_directory: file_info.is_directory
do
reset_error_code;
c_remove_directory(pathname);
end
change_directory(to: Mutable_string)
do
reset_error_code;
c_change_directory(to);
end
current_working_directory: Mutable_string
is
cwd_name.from_c(c_current_working_directory);
end
public -- general facility for removing
remove(path_name: Mutable_string)
-- feature to remove `path_name', regardless of what
path_name points too
-- if it is a file, just remove the file, if it's a directory
remove all directory entries,
-- if necessary decent further down the file-hierachy, after
deleting all entries, delete finally
-- the directory itself.
do
file_info.update(path_name);
if file_info.is_updated then
if file_info.is_directory then
fsc := new Unix_file_system_commands;
fsc.initialize;
owd := fsc.current_working_directory;
fsc.change_directory(path_name);
fsc.open_directory((".").mutable_string);
loop until not fsc.is_directory_open
fsc.next_directory_entry;
if fsc.is_directory_open then
fsc.remove(fsc.directory_entry);
endif
endloop;
error.print_string("owd = ");
error.print_mutable_string(owd);
error.print_string(", will remove ");
error.print_mutable_string(path_name);
error.print_newline;
fsc.change_directory(owd);
fsc.remove_directory(path_name);
else
error.print_string("removing ");
error.print_mutable_string(path_name);
error.print_newline;
remove_file(path_name);
endif
else
error.print_string("Could not gather information about
");
error.print_mutable_string(path_name);
error.print_newline;
endif;
end
public -- features to read in all the entry of a directory
inode: Integer
directory_entry: Mutable_string
is_directory_open: Boolean
open_directory(pathname: Mutable_string)
do
-- old_actual_file_name := file_info.actual_file_name;
file_info.update(pathname);
if file_info.is_directory then
reset_error_code;
c_open_directory(pathname);
if successful then
is_directory_open := true;
endif
else
error.print_mutable_string(pathname)
error.print_string(" is not a directory, cannot open
it");
endif
-- file_info.update(old_actual_file_name);
ensure
successful_implies_directory_opened: successful implies
not is_directory_closed
end
next_directory_entry
require
open: is_directory_open
do
reset_error_code
c_next_directory_entry;
if not dirent.is_null then
directory_entry := new Mutable_string;
directory_entry := directory_entry.from_c(c_direntry);
if directory_entry.is_equal((".").mutable_string) or
directory_entry.is_equal(("..").mutable_string) then
-- ignore current working dir and parent
next_directory_entry;
endif
-- inode := c_inode;
else
if error_code /= 0 then
error.print_string("something went wrong in
next_entry_directory" \n)
else
close_directory;
endif
endif
end
rewind_directory
-- name? move_to_first_directory_entry
-- start_directory. Don't know but the result is that
-- we restart reading all the contents of the directory
require
open: is_directory_open
do
c_rewind_directory;
end
close_directory
-- after finish
require
open: is_directory_open
do
c_close_directory;
if successful then
is_directory_open := false;
else
error.print_string("something went wrong while trying
to run close_directory" \n)
endif
end
private
ufi: Unix_file_info
fsc: Unix_file_system_commands
owd: Mutable_string
private -- Pointer for of type struct dirent.
-- to use with the *_directory features
dirent: Pointer; -- struct dirent*
dir_ptr: Pointer; -- DIR*
private -- C implementation for the *_directory features;
c_open_directory(pathname: Mutable_string)
-- open a directory for getting all the file and directory
names in it
external C_EMBED
source
"char *dir_name = (char*) ", `pathname.storage`, ";", \n
" DIR *dir = opendir(dir_name);", \n
" if (dir == NULL) {", \n
" ", `error_code`, " = errno;", \n
" }", \n
" ", `dir_ptr`, " = (_q_PointerV) dir;"
end
c_next_directory_entry
-- get next directory entry (file or directory)
external C_EMBED
source
"DIR *dir = (DIR*) ", `dir_ptr`, ";", \n
" struct dirent *dirent_ptr = readdir(dir);", \n
" if ((dirent_ptr == NULL) && (errno != 0)) {", \n
" ", `error_code`, " = errno;", \n
" }", \n
" ", `dirent`, " = (_q_PointerV) dirent_ptr;"
end
c_direntry: Pointer
-- extract the name of the entry from the `dirent_ptr`
external C_EMBED
source
"struct dirent *dirent_ptr = (struct dirent*) ", `dirent`,
";", \n
" Q_RETURN (_q_PointerV) dirent_ptr->d_name;"
end
c_inode: Integer
-- extract the inode number of the entry from the
`dirent_ptr`
-- do not know if that is useful information
external C_EMBED
source
"struct dirent *dirent_ptr = (struct dirent*) ",
`dirent`, ";", \n
" Q_RETURN (_q_IntegerV) dirent_ptr->d_ino;"
end
c_rewind_directory
external C_EMBED
source
"DIR *dir = (DIR*) ", `dir_ptr`, ";", \n
" rewinddir(dir);"
end
c_close_directory
external C_EMBED
source
"DIR *dir = (DIR*) ", `dir_ptr`, ";", \n
" closedir(dir);"
end
private -- file manipulation
c_make_hard_link(from: Mutable_string; to: Mutable_string)
external C_EMBED
source
"char *from = (char*) ", `from.storage`, ";", \n
" char *to = (char*) ", `to.storage`, ";", \n
" int result = link(from, to);", \n
" if (result != 0) {", \n
" ", `error_code`, " = (_q_IntegerV) errno;", \n
" }"
end
c_make_symbolic_link(from: Mutable_string; to: Mutable_string)
external C_EMBED
source
"char *from = (char*) ", `from.storage`, ";", \n
" char *to = (char*) ", `to.storage`, ";", \n
" int result = symlink(from, to);", \n
" if (result != 0) {", \n
" ", `error_code`, " = (_q_IntegerV) errno;", \n
" }"
end
c_unlink(pathname: Mutable_string)
external C_EMBED
source
"char *pathname = (char*) ", `pathname.storage`, ";", \n
" int result = unlink(pathname);", \n
" if (result != 0) {", \n
" ", `error_code`, " = (_q_IntegerV) errno;", \n
" }"
end
c_rename(from: Mutable_string; to: Mutable_string)
external C_EMBED
source
"char *from = (char*) ", `from.storage`, ";", \n
" char *to = (char*) ", `to.storage`, ";", \n
" int result = rename(from, to);", \n
" if (result != 0) {", \n
" ", `error_code`, " = (_q_IntegerV) errno;", \n
" }"
end
c_make_directory(pathname: Mutable_string)
external C_EMBED
source
"char *pathname = (char*) ", `pathname.storage`, ";", \n
" int result = mkdir(pathname, ACCESSPERMS);", \n
" if (result != 0) {", \n
" ", `error_code`, " = (_q_IntegerV) errno;", \n
" }"
end
c_remove_directory(pathname: Mutable_string)
external C_EMBED
source
"char *pathname = (char*) ", `pathname.storage`, ";", \n
" int result = rmdir(pathname);", \n
" if (result != 0) {", \n
" ", `error_code`, " = (_q_IntegerV) errno;", \n
" }"
end
c_change_directory(to: Mutable_string)
external C_EMBED
source
"char *to = (char*) ", `to.storage`, ";", \n
" int result = chdir(to);", \n
" if (result != 0) {", \n
" ", `error_code`, " = (_q_IntegerV) errno;", \n
" }"
end
c_current_working_directory: Pointer
external C_EMBED
source
"char *cwd = NULL;", \n
" char *getcwd_result = NULL;", \n
" size_t size = PATH_MAX; /* arbitrary start length */", \n
" do {", \n
" cwd = gc_malloc (size);", \n
" errno = 0;", \n
" getcwd_result = getcwd (cwd, size);", \n
" if (!getcwd_result){", \n
" if (errno == ERANGE){", \n
" /* size was too small */", \n
" gc_free(cwd);", \n
" size *=2;", \n
" } else {", \n
" abort (); /* should not occur */", \n
" }", \n
" }", \n
" } while (!getcwd_result);", \n
"", \n
" Q_RETURN (_q_PointerV) cwd;"
end
endclass
As you can see it is remarkable seamless and clear to read, we think every other way is simpyl to tedious and well generating tons of code is not really that well understandable
Now here's some "pure" Q code
class String_splitter
public
initialize
do
reset
set_delimiter((" ").mutable_string)
delimiter.extend('\t')
delimiter.extend('\n')
end
set_delimiter(pattern: Mutable_string)
do
delimiter := pattern.twin
ensure
delimiter_set: delimiter.is_equal(pattern)
end
work_on(str: Mutable_string)
do
reset
on_this := str.twin
end
after: Boolean
is
index > on_this.count
ensure
after_contraint: Result implies index > on_this.count
end
reset
do
index := 1;
ensure
was_reset: index = 1
end;
item: Character
is
on_this.item(index)
ensure
proper_item: Result = on_this.item(index)
end
next
-- get the next element
local
i: Integer
done: Boolean
do
done := false
skip_delimiters
last_mutable_string := new Mutable_string
loop until after or done
if not delimiter.has(item) then
last_mutable_string.extend(item);
index := index + 1;
else
done := True
endif
endloop
end
skip_delimiters
-- skip chars which are elements of `delimiter'
local
done: Boolean
do
done := false
loop until after or done
if delimiter.has(item) then
index := index + 1
else
done := True
endif
endloop
end
last_mutable_string: Mutable_string;
last_string: String
is
last_mutable_string.string
end
private
delimiter: Mutable_string;
on_this: Mutable_string;
index: Integer;
error: Standard_error;
endclass
I'd argue this is easy to understand code
© Q-Software Solutions GmbH
1999-2010
Terms and
Conditions