syntax in ghūl
projects and files
A ghūl project is composed of a set of ghūl source files. Source files should have a .ghul file extension, and must be UTF-8 text.
Each source file can contain zero, one or more global definitions. Definitions can be in any order and in any file. Source files can have any name, provided they have a .ghul extension, and can be in any folder under the project root (subject to any source file glob pattern given in the .ghulproj)
A file with no namespace can mix definitions with statements at the top level, so a whole program can read as a script with no explicit entry function. These are called top-level statements.
tokens and trees
Source files are translated into various kinds of tokens. Some tokens are a fixed sequence of characters (like the keyword while). Others are composed of characters according to various rules (identifiers, strings, numbers etc.)
With a couple of exceptions, ghūl tokens are similar to most common programming languages. The exceptions are:
operators
Operators are any contiguous string of operator characters. This is only significant in the rare case where running together the characters that comprise two different operators might not have the result you expect
escaped identifiers
A leading backtick escapes a keyword or operator so it can be used as an ordinary identifier: `while is the identifier while, and `+ is the identifier +. The backtick is not part of the escaped name, so escaping a name that is not a keyword, like `count, means the same as plain count. A backtick is only meaningful immediately before an identifier, operator, or opening bracket; anywhere else it is a dangling-backtick error.
block structure
ghūl is a block structured programming language. Source code in ghūl is composed of blocks, typically many of them, with blocks nested inside other blocks.
Blocks are delimited by keywords. The keywords that begin and end a block are specific to each different kind of block. This way of delimited blocks is descended from the ALGOL family of languages, most specifically from ALGOL 68. It has the advantage of making the block structure clearer, both to someone reading the code and to the compiler.
x <= y
In this example then, else and fi all delimit blocks. The blocks they delimit contain statement lists, and they do so whether the if is used as a statement or as an expression - see every arm is a statement block.
semicolons
A semicolon separates two statements or definitions written on the same line. That is the only place one is needed. At the end of a line the line break ends the statement, and end of file ends the last line, so code written one statement to a line has no semicolons in it. The examples on this site are written that way.
A semicolon at the end of a line is accepted, and the program is the same with or without it: a function body's tail value is decided by its type, not by whether its last statement is terminated. The compiler reports an end-of-line semicolon as a redundant-semicolon warning. --inlay terminator shows the statement boundaries the parser inferred as editor inlay hints.
Adjacent string literals join into one literal, across a line break as well as within a line. Where a statement ends on a string literal and the next line begins with one, a semicolon between them keeps the two apart. redundant-semicolon does not report that one.
A line break ends a statement only where the statement is complete. An expression left unfinished at the end of a line, such as a + or an open (, continues on the next line, and a line beginning with . or |> continues the line above, so member chains and pipes wrap in the usual way. Code formatted in the conventional way parses as you would expect. The full rules are under statement terminators in the grammar, for the occasions when a wrapped expression parses differently from how it reads.
definitions and statements
Blocks in ghūl can contain definitions, statements, or a mix of both. Which is permitted in a given block depends on the type of block.
file structure
At its top level a ghūl source file contains definitions and use directives; a file with no namespace can also contain statements. There is no required ordering and no file header.
hello, world
The definitions in a file can be global functions, properties, classes, structs, traits, unions and enums, or namespace blocks that group definitions under a name. A definition is visible to the rest of the project regardless of which file it appears in, so how source is split across files is purely a matter of organisation.
A file that declares any namespace must place all of its definitions inside namespaces. A file with no namespace at all has its definitions placed in a private namespace of their own, which is convenient for small programs and tests. Namespaces, use and symbol visibility are covered in full under definitions.
top-level statements
A file with no namespace can also have statements at its top level. They run in source order as the program's entry point, so a short program needs no entry function:
Definitions in the same file are still hoisted, so a top-level statement can use a function or type declared anywhere in the file. Top-level statements and a namespace cannot appear in the same file.
Otherwise, execution of a program begins at a function named entry.