Notice that this documentation is a very brief introduction to CASC programming language's syntax and several surface-level language \ specification, which expects that you have some experience with Rust's, Go's, V's, Java's, or Kotlin's syntax.
CASC is a programming language focus on concise syntaxes and enhancing language features from Java. It is mostly inspired by Rust / Go Lang / V Lang, and influenced by Kotlin. CASC features:
- Fully interoperability to Java
- Shorthand syntaxes
- Rust / Go Lang / V Lang inspired syntaxes
- Better class form organization
CASC uses .casc and .cas as source file extension name.
For 0.1, please heads to CASC github repository's release for more infomation.
- Compile
java -jar <path to CASC compiler jar file> [-o "C:/outputDirectory/"] <source file / project source folder> [-t]
Available parameters
Enable timing: -t
Shows all compiler unit's progressing total time
- Compile and Run
java -jar <path to CASC compiler jar file> run [-o <output directory>] <source file> [-t]
Available parameters
Enable timing: -t
Shows all compiler unit's progressing total time
This is 0.1.0 version's example of Hello World.
To get start with any languages, hello world is the best demonstration to show a language's features. The following code is CASC's basic Hello World application:
class CASC
impl CASC {
fn main(args: [str]) {
java::lang::System.out.println("Hello World!")
}
}
Let's take a look at the code above: First, class
keyword declares a public class in default,
it's necessary for CASC 0.1.0 to compile into class file. Second, similar to Java's main function,
you'll need to declare main function. Since we still haven't implemented an easier way to declare argument-less
main function, you'll need to add args: [str]
into arguments in order to let JRE identify it as the entry function.
CASC Primitive Types | Java Primitive Types |
---|---|
bool | boolean |
char | char |
i8 | byte |
i16 | short |
i32 | int |
i64 | long |
f32 | float |
f64 | double |
str | java.lang.String |
unit | void |
null | null |
Notice that null
is not an actual type in CASC, it's just a placeholder for typing, which means you cannot declare null
as a real type.
To actually declare functions, you'll have to declare it in Impl block.
Similar to Kotlin's (or V Lang) function, you declare a function like below:
fn add(a: i32, b: i32): i32 {
return a + b
}
To declare it as a companion function (same as static function in Java), simply declare like below:
fn add(a: i32, b: i32): i32 {
return a + b
}
To declare it as a member function, you'll need to add keyword self
before all the other parameters:
fn add(self, a: i32, b: i32): i32 {
return self.manipulateAddition(a, b)
}
In addition, if you have type to return, CASC doesn't allow you to return last expression without having return
keyword:
fn add(a: i32, b: i32): i32 {
return a + b
}
To declare a void function, you can simply change return type to unit
or just don't add return type after a function declaration:
fn sayHi(): unit {
println("Hi")
}
fn sayHi() {
println("Hi")
}
Access modifiers could be applied on classes, fields, and functions.
CASC Access Modifiers | Java Access Modifiers |
---|---|
pub | public |
intl | (default access modifier) |
prot | protected |
priv | private |
In default, every class, fields, and functions without access modifiers are public.
Variable declaration is very straightforward in CASC, unlike Java, CASC doesn't need you to annotate variable's type while declaring:
a := 1 // Varible a stores i32 data
To make it mutable, add mut
keyword before the variable name:
mut a := 1 // Variable a is now able to be assigned with other i32 data
Notice that CASC doesn't allow you to declare a variable with annotating a type, in other word, CASC always infers the right-hand expression's type, this can be done through a set of promotion rules, see Promotion
(See Types for more information)
Primitive number types can be promoted into another primitive number type only if they won't lose precision of their current data:
i8 -> i16 -> i32 -> i64 -> f32 -> f64
⇕ ⇑ ⇑ ⇑ ⇑ ⇑
java::lang::Byte ⇓ ║ ║ ║ ║
java::lang::Short ⇓ ║ ║ ║
java::lang::Integer ⇓ ║ ║
java::lang::Long ⇓ ║
java::lang::Float ⇓
java::lang::Double
Promotion is automatically done by compiler, if your losing precision is expected behavior, see Casting (WIP).
Assignment expression allows you to assign to various variable within a single expression:
a = b = 1 // Ok
Assignment expression will retain the latest value if necessary.
However, you should notice that assignment will also trigger Promotion when left-hand expression is narrower data type than right-hand's:
mut a := 1I // a stores i32 data: 1
mut b := 2F // b stores f32 data: 2.0
// Err, cannot assign a wider data type to narrow data type, (f32 -> i32)
a = b = 1
// the execution order is (b = 1) then (a = b), at first assignment, 1 has been promoted as f32, so at second assginment
// it would fail.
// Ok
b = a = 1
Array in CASC has different similarity aspects compared to Rust, C# or Java.
Array type is very similar to Rust's, however since JVM doesn't force parameter array to have specific size, therefore, it's still kinda different from Rust's:
[str] -> 1D string array
[[str]] -> 2D string array
// and so on...
But array declaration / initialization has mixed style with Rust's, C#'s and Java's:
[i32; 10]{} // Array declaration
[i32; cap]{} // Array declaration with varaible-determined size
[;]{ 1, 2, 3 } // Array initialization
[i32;]{ 1, 2, 3 } // Array initialization with sepcifying array type
Notice that you must have the top dimension declare with a size, otherwise, CASC couldn't know how many array elements allocate.
[i32;]{} // Err, missing dimension at the top dimension declaration
Furthermore, you can skip declaring other sub-dimensional arrays with size, which means this array will be a jagged array.
[[i32;];10]{}
// Equivalent to Java's:
// new int[10][];
There's nothing special about package, it just works like Java:
package your::package::here;
use
keyword is very similar to Java's import, but its syntax is much similar to Rust's:
use other::package::path
use other::{ package, package::path }
use other::pacakge::path as p
Notice that CASC doesn't support companion use
at this moment, similar to Java's static import
.
(Notice that CASC also has if-else expression)
If-else statement allows you conditionally execute code through multiple branches:
if i > 1 {
} else if i > 2 {
} else {
}
CASC supports Java-favored for loop, though the limitation is that you can only have 1 statement in pre- / post-init section:
for mut i := 0; i < 10; i++ {
}
You can declare string literal with ""
:
"This is a string!"
Same for character literal (''
):
'a'
To escape a character, put \
before the character you want to escape:
"\t\r"
The following list shows the possible escaping characters:
Escaped character | Name |
---|---|
\t | Tab |
\\ | Back slash |
\r | Carriage return |
\n | Newline |
\b | Backward |
\u09AF | 16-bit unicode character |
1 // Integer Literal
2.0 // Float Literal
Besides, the normal literal, CASC also supports type annotated integer / float literal:
1B // Integer Literal (i8)
1S // Integer Literal (i16)
1I // Integer Literal (i32)
1L // Integer Literal (i64)
1F // Float Literal (f32)
1D // Float Literal (f64)
true
false
null
Operators in CASC is as same as Java's, including their precedence, see Java's operator tutorial page for more information.
Yeah, I'm quite lazy here to list all operators :P, I'll list it later
If-else expression is very similar to Java's and C#'s conditional expression, or Rust's and V's if-else expression. If-Else expression works mostly like If-Else Statement, except in most scenarios, it expects having an expression returns (or retain) value:
a := if i > 1 {
1
} else {
2
}
If-else expression will also perform promotion if the value types at the end of branches is different.
See Assignment.
aka constructor invocation
Same as Java's constructor expression (or invocation):
new ClassName(/* parameters */)
Class is just like struct from various languages, it's meant to hold data and various function. But unlike struct, class in
modern OOP languages have more extended functionalities, such as member functions.
To declare a class in CASC, you have two ways:
- No scope
class ClassName
- With scope
class ClassName {
}
Notice that no scope
cannot be used while there's some field declarations, to declare fields, you'll have to use
with scope
.
Class could be applied with access modifiers and mut
keyword:
pub mut class ClassName
mut
keyword is used for inheritance situation: in CASC, all declarations are defaulted as non-mutable, in order to make
child class able to override class, you'll have to add mut
keyword before class
keyword.
CASC introduces a simple (or more neat) way to declare fields, inspired by V's (and Go's) field syntax:
class ClassName {
// start with empty accessors or `pub:`
pubFinalField: i32
mut:
// or `pub mut:`
pubMutableField: i32
prot:
protFinalField: i32
prot mut:
protMutableField: i32
intl:
intlFinalField: i32
intl mut:
intlMutableField: i32
priv:
privFinalField: i32
priv mut:
privMutableField: i32
}
To declare a set of companion fields, wrap it in a comp
block:
class ClassName {
comp {
// Your fields here
}
}
Notice that you cannot wrap it more than one times or have more than one comp
blocks, otherwise, compiler will panic about this.
Impl, or implementation, is a workaround for Java's, or Kotlin's function organization issue, its syntax is inspired by Rust's,
impl
keyword can split owner functions and implemented interface function implementations, so you can straightforwardly know which functions
belong to which interface. This also overhauls the tidiness of a class implementations.
Same as Class, you have no scope
and with scope
options to declare an implementation block, the limitations are same.
But implementation block is actually optional, you can declare a class without it.
impl ClassName {
}
See Function for more basics.
Function declaration also accept access modifiers and mut
keywords.
CASC introduce a concise way to declare a constructor:
new(/* parameters */) {
}
The above example declares a constructor, notice that if your class inherit other class, you'll have to declare a constructor
with a self
keyword to call parent class' constructor, and all the other constructors in same context should have super
keyword to call constructor with self
keyword:
impl ClassName: ParentClass {
// This calls parent class' `new()`
new(): self() {
}
// This calls `new(): self()`
new(i: i32): super() {
}
}
See Inheritance (Class) for more information.
Like other OOP languages, CASC has inheritance!
impl ClassName: ParentClass {
}