Introduction

Panda is a clear and stylish programming language for JVM based on top of the Panda Framework, focusing on safety, simplicity and extensibility.

Note: At this moment Panda is at an early stage of development and it is not recommended to use it in any kind of software that requires stability.

Who Panda is For

The purpose of the Panda is to reduce boilerplate code written in Java and improve the safety of various aspects of Java language using modern solutions. It is also a default implementation of Panda Framework and presentation of some of its possibilities. Let's see who may be interested.

‣ Beginners

The language and ecosystem is simple and friendly for people who just stared their journey with software development. Tools like REPL or implementation like Light should make this path easier.

‣ Open Source Developers

Open Source is most of all a community that work together to reach the same goal. Be one of the first people who may create awesome libraries or contribute to the existing projects related with Panda.

‣ Developers

Enhance your project by scripts written in Panda or create your own language on top of the Panda Framework. Implement in a few steps language that is suited to your case instead of trying to force existing solutions to your needs.

When We Discourage

As always, there is no perfect technology and here is a list of disadvantages that should be considered against using projects based on Panda Framework

  • Handling huge amount of primitive-based data
  • Heavy math operations (it's also related to the first point)

Panda does not support primitive types, each operation on this kind of data causes autoboxing. In a standard code it does not really matter, but in case of any listed situation it may be a bottleneck.

Installation

At this moment we offer two ways to install Panda. The first one is dedicated for users and developers who use and create applications in Panda. The second on is for developers who want to use Panda Framework as a scripting language in theirs applications.

‣ General

As we said, Panda runs on JVM (Java Virtual Machine). We have prepared installer with wrapped JVM to get rid of problems related to some kind of invalid/custom installations of an existing machines.

./panda-installer.exe

‣ Developers

Panda supports various implementations of JVM and all their versions above 8+

  • Oracle HotSpot
  • OpenJDK
  • GraalVM (Native images are not supported yet)
  • DCEVM

As a standard way to serve Java library, we host Maven artifact located in our own repository called Panda Repository:

<repository>
    <id>panda-repository</id>
    <name>Panda Repository</name>
    <url>https://repo.panda-lang.org/</url>
</repository>

The artifact may be found as:

<dependency>
    <groupId>org.panda-lang</groupId>
    <artifactId>panda</artifactId>
    <version>{version}</version>
</dependency>

Tools

Project is still unstable and there is no official and advanced tools yet. You may take a look at these concepts:

  • Light - English-like programming language built using the Panda Framework
  • Reposilite - Repository management software based on Spring mainly dedicated for Maven and Panda-based artifacts
  • Lily - a modular and lightweight IDE for languages based on Panda Framework

We don't recommend to use API of Panda Framework before beta release.

Project

Projects in Panda should follow the standardized structure. Create a directory for your new project called e.g. awesome-project:

$ mkdir awesome-project
$ cd awesome-project

Every proper project should contain at least 2 files:

  • panda.hjson - project configuration file with some details about the current project
  • app.panda - main script file (Name of this file can be changed in a configuration)

Let's see how it should look like:

panda-project/     # project directory
  src/             # sources
    app.panda      # main script file
  tests/           # <concept> integration tests
  resources/       # <concept> non panda files
  panda.hjson      # project configuration file
  README.md        # project description for GitHub and Reposilite pages 

Configuration file panda.hjson

This file describes our project and the way how it works. It uses JSON-like format called 'JSON for Humans'. Standard configuration file should contain name, version, author and main key-value properties:

name: awesome-project
version: 1.0.0
owner: dzikoysk

scripts: {
    main: src/app.panda
}

You may also declare dependencies by adding the dependencies section:

dependencies: [
    // <source>:<author>/<name>@<version>
    github:dzikoysk/[email protected]
]

Writing and Running a Panda Program

Panda files always end with .panda extension. You could already notice that we've declared our main file in project configuration as app.panda in directory src, so let's create it now:

[email protected] ./project-awesome/
$ mkdir src
$ touch src/app.panda

Next, add the following code to your app.panda file. You can use your favorite editor like e.g. Visual Studio Code.

main {
    log 'Hello, Panda!'
}

You can assume that the code that you have already used will print 'Hello, Panda!' message in the console. Don't worry, we will analyze this code later. Let's check the result - to launch the awesome-project you need just one more step:

[email protected] ./project-awesome/
$ panda panda.hjson
Hello, Panda!

--- OR: you may call src/app.panda directly (it ignores project configuration file)

$ panda src/app.panda
Hello, Panda!

Congratulations! You've officially written a Panda program. That makes you a Panda programmer - welcome!

Basics

Many programming languages have much in common at their core. This chapter is a list of available structures and expressions in Panda. They are ordered in a way that may help during the learning process.

Comments

Comments are statements in our code that the interpreter ignores. We generally use them to explain the code. It means comments won't be a part of the program and it will not be executed.

// single line comment

/*
Multiline
Comment
*/

/**
 * Documentation comment
 * It's used to generate documentation of your program in a standardized way
 */

Main

The main block is a launch station of your application. It is called by the interpreter and it is always the first code that runs in every executable program.

main {
    // code goes here
}

Remember - You should specify file with a main statement in your project configuration file.

Basic types

Data type identifies and describes values used in your code. By default, we have types that identifies numbers, texts or logical values.

‣ Numbers

Integral numbers are defined by the Int type:

123
-321

In the similar way we can use floating-point numbers represented by the Float type:

1.0
-0.1337

There are also other number types like Byte, Short, Long and Double. You will learn about them in the future.

‣ Text

In programming, the type that describes text is called String. As of today, every time you want to use some sequence of characters in your program, you have to call it String. We can declare String in two forms:

// preferred way of declaring strings, 
// we can use quotation marks inside of the apostrophe string
'Michael Scott said "I love inside jokes. I hope to be a part of one someday."'

// useful when we want to use apostrophe in string
"That's what she said"

// useful when we want to mix ' and " 
`"Two things are infinite: the universe and human stupidity; and I'm not sure about the universe."`

‣ Logic

To represent true and false states we need to use the boolean data type. The boolean values in Panda are represented by the Bool type.

‣ Arrays

Array is a collection of values with a predefined size. It is well-known structure in programming, but if you haven't never had a contact with any programming language, you can imagine it as a primitive form of list.

To create an array, you have to use [] operator:

String[] array = new String[5] // array of 5 elements
array[0] = "First element in array" // store string value at position 0 in array
log array[0] // print value at position 0

Log

In a clear way, logging is just a fancy word to define a process of writing down what you do. It is utility statement, used in most cases to display list of expressions to a default output of your application (e.g. console).

log 'Hello', 'Panda!'

Despite this, serious projects should still consider custom logging libraries for a fuller experience.

Modules

Every piece of your application should belong somewhere. This place is called module and it is defined on top of your file.

module awesome-app

// for submodules

module awesome-app:submodule

Names of modules supports:

  • letters a-zf
  • digits 0-9
  • dashes -
  • colons : (to separate submodules)

Require

As you could see, we can define modules related to some parts of our application. It is a standard way to distribute utilities by libraries, to use delivered components we need to import them using the following structure:

// import java collections from std/module path
require java:collections

// import 'local-module' module in directory ./local-module
require 'local-module'

The require statement supports also single files:

// import './local-file.panda' file
require 'local-file'

Import

Import works like the require statement, but for Java classes. Using this structure you can easily use types from Java, they are mapped automatically.

import java.lang.Thread

Note: Imported classes are not visible in another files. To make imported class visible use export statement.

Export

To improve overall scripting experience and simplify access to the Java types, you can use the export statement. The statement works like import, but it also publishes the result to the other participants like files and modules that imports this module.

module extra-java-bindings

// export StringUtils class, the class will be visible in 
export org.panda_lang.utilities.commons.StringUtils

It may be helpful during the process of creating wrappers and bindings modules for existing Java apps and libraries.

Variables

A variable is a symbolic name for your information (value). Every variable must be declared. Any attempt to use a variable that hasn't been declared yet is a syntax error. By default, variables in Panda are:

  • Immutable - once assigned value cannot be changed in the future
  • Null safety - you are not allowed to assign null (not existing) values
String message = 'With Great Beard Comes Great Responsibility'

‣ Mutable

Sometimes there is a need to change/update value of some variables. To make your variable mutable just add mut keyword

mut String messageOfTheDay = 'Productivity'

if true {
    messageOfTheDay = 'Cat Videos'
}

‣ Nullable

In some evil scenarios you may be also forced to handle null values (e.g. during the interaction with Java libraries).

nil String playingWithFire = null

Remember: You should avoid null values, it is really bad practice to operate directly on a value that in fact does not even exist. Null values are also known as The Billion Dollar Mistake. Panda supports null values only because of the compatibility requirements with Java layer.

‣ Late

Let's say you want to assign value to a variable a little later due to e.g. some kind of condition. You may use late keyword to have a guarantee that the variable was assigned before its usage. You can also still benefit from the immutability of this variable.

late String conditionalValue

if true {
    conditionalValue = 'a'
}
else {
    conditionalValue = 'b'
}

Operators

Operators are these all strange combinations of ~!=-+:/|()%&* characters that do some basic stuff like e.g. comparing two values. This article should be a part of the Chapter 5 about expressions, but we need to explain it now to clarify some of the next basic structures.

‣ Arithmetic Operators

Returns Number as a result of operation

Operator Name Description Example
+ Addition Adds together two values x + y
- Subtraction Subtracts one value from another x - y
* Multiplication Adds together two values x * y
/ Division Adds together two values x / y
% Modulus Adds together two values x % y
++ Increment Increases the value of a variable by 1 i++
-- Decrement Decreases the value of a variable by 1 i--

‣ Assignment Operators

Returns assigned value as a result

Operator Description Example
= Assign value to a variable x = y
+= Add value to a variable x += y
-= Subtract value from a variable x -= y
*= Multiplicate value of variable x *= y
/= Divide value of variable x /= y

Remember: Variables modified by these operators have to be mutable.

‣ Comparison Operators

Returns true or false as a result:

Operator Name Example
== Equals to x == y
!= Not equals to x != y
> Greater than x > y
< Less than x < y
>= Greater than or equal to x <= y
<= Less than or equal to x >= y

‣ Logical Operators

Returns true or false as a result:

Operator Name Description Example
&& And True if both statements are true (x == 0) && (y == 0)
|| Or True if one of the statements is true (x == 0) || (y == 0)
! Not Reverse the result value !(x == 0)

Conditions

Conditions performs different computations or actions depending on a provided value:

String slogan = 'I’m lovin’ it'

if slogan == 'Taste the rainbow' {
    // do something if the slogan variable is equals to the 'Taste the rainbow' text
}
else if slogan != '' {
    // do something if slogan is not empty
}
else {
    // do something if all of the above conditions hasn't been succeed
}

As you might suppose, the do something if slogan is not empty section will be performed.

Loops

Loops are used to execute a set of statements repeatedly until a particular condition is satisfied. Panda supports 4 types of loops at this moment.

‣ Loop

You can just easily loop x times using the standard loop statement:

loop 5 {
    log 'ฅ^•ﻌ•^ฅ' // this code will be performed 5 times
}

‣ For

The most popular for-loop in almost every programming language. The pattern of for syntax is as follows:

for (initialization; termination condition; increment) {
    statement(s)
}
  • initialization - you can declare auxiliary variable here. It's executed only once, as the loop begins
  • termination condition - as long as this condition is true, the loop will be performed
  • increment - this expression is invoked after each iteration through the loop

An example implementation of this statement might looks like:

for (mut Int index = 1; index < 2; index++) {
    log index
}

Note: You can also skip some of these expressions or even all of them:

for (;;) {
    log 'What's happening?!'
}

You've just created the infinite loop!

‣ For each

You can also use foreach loop to iterate over iterable structures (in most cases just collections):

foreach (String element : collection) { 
    log element
}

‣ While

Another popular loop called while-loop. The while loop loops through a block of code as long as a specified condition is true.

mut active = true

while active {
    log 'Active: ' + (active = false)
}

Note: As an alternative to infinite loops based on for (;;) { }, you can just use while true { } ✧ʕ̢̣̣̣̣̩̩̩̩·͡˔·ོɁ̡̣̣̣̣̩̩̩̩✧

Branching statements

Branching statements interfere the control flow of your code. Using these statements you can easily return values and interrupt some actions.

‣ Return

Using the return statement you can easily interrupt execution of current scope.

main {
    if true {
        return // the main scope will be terminated here
    } 

    // this code is unreachable
}

You are also able to return a value. For instance, method getMessage() that returns String (don't worry, we will explain methods later)

shared getMessage () -> String {
    return 'Hello Panda' // return value
}

‣ Continue

The continue statement allows us to skip current iteration of loop. Because of this fact, it works only in loops.

for (mut Int index = 0; index < 10; index++) {
    if (index % 2) == 0 {
        continue // skip even numbers
    }

    log index
}

// prints 1, 3, 5, 7, 9

‣ Break

The break statement works like return for loops. Using the break you can terminate flow of execution.

while true {
    if ThreadLocalRandom.current().nextInt(0, 100) == 50 {
        break // break infinite loop if we draw number 50 🤡
    }
}

Throw

An exception is an event, which occurs during the execution of a program, that disrupts the normal flow of the program's instructions.

What does it mean? In case of any unwanted situations (like passing invalid data), we can interrupt execution of our application using the throw keyword.

Let's say we want to make sure that nobody will perform division by zero:

Int by = 0 // user data

if (by == 0) {
    throw new Exception('Cannot divide by zero') // throw error
}

log 100/by

These exceptions are fully compatible with Java exception system.

Try

We've just talked about throwing exceptions. It might be very confusing to understand, why would you throw these strange exceptions, if you could just simply log an error message and return. The whole thing is that actually, we can catch these exceptions using the try-catch block:

Int by = 0 // user data

try {
    if (by == 0) {
        throw new Exception('Cannot divide by zero') // throw error
    }

    log 100/by
} 
catch (Exception e) {
    log e.getMessage() // print message of the caught exception
}

Using this try-catch you can also catch exceptions thrown in Java sources.

Types

Types describe values and its behaviors. You've already used some of them like String and Bool.

It's time to create a new one, let's say a Cat type. To declare a new type, we need to use type keyword:

type Cat {

}

To use a new type, you have to create a new instance using the new {TypeName}() structure:

Cat garfield = new Cat()
Cat grumpy = new Cat()

In the following articles about fields, methods, constructors and many others, we will enhance this example.

Remember: Members of types (like fields or methods) are called properties. Some of them can by called using the () structure. We can also specify some extra data between this brackets - these values are called arguments.

Visibility

In fact, before we will start talking about all the mechanisms associated with types, we need to introduce the visibility system.

Visibility allows us to control access to various properties declared by types. At this moment Panda supports 3 visibilities:

  • open - we can access these properties wherever we are
  • shared - only module that owns this type, and its submodules, can access these properties
  • internal - only type and its inheritors can access these properties

Because of the fact that it might be hard to understand for beginners, we will declare every property as open for the purposes of the guide.

Constructors

Constructor is a block of code executed when we create an instance of our type.

type Cat {
    constructor (String name) {
        log name
    }
}

main {
    // new Cat() performs constructor()
    Cat garfield = new Cat('Garfield')
    Cat grumpy = new Cat('Grumpy Cat')
}

If we do not specify a custom constructor, Panda will generate the empty one constructor () { } that just does nothing.

Fields

To put it simply, field is just a variable that belongs to the type. The pattern that defines fields is as follows:

{visibility} {type} {name}

Let's say we want add field with a name of our cat to store this information:

type Cat {
    // {visibility} {type} {name}
    open String name

    constructor (String name) {
        // to distinguish variable name with field name, 
        // we can use 'this.` prefix to indicate field
        this.name = name
    }
}

main {
    Cat garfield = new Cat('Garfield')
    Cat grumpy = new Cat('Grumpy Cat')

    // we can access field 'name', so let's print its value
    log garfield.name, grumpy.name
}

Methods

A function is a block of organized, reusable code that is used to perform a single, related action. Functions that belong to types are called methods. The pattern that defines methods is as follows:

{visibility} {method name} ( {parameters} ) -> {return type} {
    // code
}

Let's replace direct access to field name with a method getName():

type Cat {
    internal String name

    constructor (String name) {
        this.name = name
    }

    // {visibility} {method name} ( {parameters} ) -> {return type}
    open getName () -> String {
        return this.name
    }
}

main {
    Cat garfield = new Cat('Garfield')
    Cat grumpy = new Cat('Grumpy Cat')

    // we can call methods just like that:
    log garfield.getName(), grumpy.getName()
}

Static

Fields and methods you've already used in the previous articles were called and accessed using some object instances. There is also a way to declare properties that do not need an instance to work. This kind of properties are called static and are often used to declare some constant values and utilities.

‣ Fields

It is good practice to use uppercase names for static fields:

type Mars {
    open static Float RADIUS = 3389.5
}

main {
    // we don't need to create instance of Mars
    // to access RADIUS field coz it is static
    log Mars.RADIUS 
}

‣ Methods

type Math {
    // find highest value of a and b
    open static max (Int a, Int b) -> Int {
        if (a > b) {
            return a
        }

        return b
    }
}

main {
    // we don't need to create instance of Mars
    // to access max method coz it is static
    log Math.max(-10, 100)
}

Inheritance

Inheritance is a concept where we can share common properties between various types.

We've already defined type Cat. If we'd like to create a new type Dog that behaves exactly like Cat, we could just copy-paste its content. It is a little silly and exhausting solution, especially in larger projects.

Let's think about common characteristics of Cat and Dog. For instance, they're both animals, so it is time to create the Animal type:

type Animal {
    internal String name

    constructor (String name) {
        this.name = name
    }

    open getName () -> String {
        return this.name
    }
}

To inherit Animal we need to extend it using the type {Name} : {Type to extend}:

type Cat : Animal { }

type Dog : Animal { }

Voilà! 🥖

Note: Unfortunately, we can extend only one type. This limitation is a result of problems that comes with a various conflicts between shared properties.

Base

The main problem in inheritance is to support compatibility of object initialization process. Literally speaking, we need to guarantee call to the base (parent) constructor.

We will use previous example to explain and fix lack of base call:

type Animal {
    constructor (String name) { }
}

type Cat : Animal {

}

By default, every type without specified constructor has empty constructor without parameters. It means that in fact, Cat looks like this:

type Cat : Animal {
    constructor () { }
}

As you can see, we have conflict between constructors. Calling new Cat() we cannot perform new Animal('name of animal') due to lack of name parameter. It's time for the base statement:

type Cat : Animal {
    constructor (String name) { 
        base(name) // pass value to parent constructor 
    }
}

Fixed 👍

Interfaces

The interface is a list of requirements that type has to contain. One type can implement several interfaces.

interface AnimalType {

    getAnimalType () -> String
    
}

Implementation of interface looks exactly the same as extending another type:

type Cat : Animal, AnimalType {

    override getAnimalType () -> String {
        return 'Cat'
    }

}

As you can see, we've used strange new keyword - override. In Panda, we have to mark overridden (declared by other types) methods and thanks to that, we can avoid unintentional overrides.

Expressions

Welcome to the chapter 5 🤠. It's been a while to get here. You've already learned some expressions... Wait, we didn't explain what expression really is.

To put it simply, expression is a statement that returns something as a result. Some of expressions that we used in previous chapters:

'Hello'
123
true
10 != 20
index++
new Cat()
cat.name
dog.getName()

As you can see, all of them return value that we can e.g. assign to a variable. In this chapter we will show you other useful built-in expressions.

Concatenation

String concatenation is the operation of joining string values.

log 'We can add values ' + "as strings and even other objects like"

We can also concatenate other types:

log 'Value: ' + 1000

You should be careful, for instance let's consider this case:

log '🤔: ' + 1 + 2

The following code prints 🤔: 12. To avoid strange behaviors we stringify all the parameters without exceptions. To perform some kind of custom actions like math operations, you have to wrap the operation into the ( ) brackets:

log '🤔: ' + (1 + 2)

Number types

List of available number types in std:

  • Byte represents values between -128 to 127, defined as 123B
  • Short represents values between -32,768 to 32,767, defined as 123S
  • Int represents values between -2^31 to 2^31-1, defined as 123
  • Long represents values between -2^63 to 2^63-1, defined as 123L
  • Float represents values between 32-bit floating point specified in Java Language Specification, defined as 123.0F
  • Double represents values between 64-bit floating point specified in Java Language Specification, defined as 123.0D

To access primitive wrappers for primitive types, use Primitive{Type} types. It's sometimes necessary during the access to Java API:

PrimitiveChar[] primitiveArray = '#onlypanda'.toCharArray()
Arrays.sort(primitiveArray)
log new String(primitiveArray)

Developers

Note: This chapter is dedicated only for developers that want to wrap Panda in their projects. It is not a part of the language guide.


Warning: API is not stable


To be continued ฅ^•ﻌ•^ฅ