Skip to content

Syntax Quick Reference#

# jac is a superset of python and contains all of its features
# You can run code with
# jac run <filename>

# Single line comment
#*
    Multi-line
    Comment
*#

# Import declaration declares library packages referenced in this file.
# Simple imports
import os;
import sys, json;
import time;

# Import with alias
import datetime as dt;

# Import from with specific items
import from math { sqrt, pi, log as logarithm }


# functions are defined with the def keyword and a control block
def nextFunction {
    x = 3;     # Variable assignment.
    y = 4;
    (add, mult) = learnMultiple(x, y);       # Function returns two values.
    print(f"sum: {add} prod:{mult}");        # values can be formatted with f-strings
    learnFlowControl();
}

# same as Python, Jac supports default paremeters and multiple return values
def learnMultiple(x: int, y: int = 5) -> (int, int) {
    return (x + y, x * y); # Return two values.
}

def learnFlowControl() {
    x = 9;

    # All control blocks require brackets, but not parentheses
    if x < 5 {
        print("Doesn't run");
    } elif x < 10 {
        print("Run");
    } else {
        print("Also doesn't run");
    }

    # chains of if-else can be replaced with match statements
    match x {
        case 1:
            print("Exactly one");
        case int() if 10 <= x < 15:
            print("Within a range");
        case _:
            print("Everything else");
    }

    # Like if, for doesn't use parens either.
    # jac provides both indexed and range-based for loops
    for i = 10 to i <= 20 by i += 2 {
        print(f"element: {i}");
    }

    for x in ["a", "b", "c"] {
        print(f"element: {x}");
    }

    # while loops follow similar syntax
    a = 4;
    while a != 1{
        a /= 2;
    }

    learnCollections();
    learnSpecial();
}

def learnCollections() {
    # lists and slicing
    fruits = ["apple", "banana", "cherry"];
    print(fruits[1]); # banana
    print(fruits[:1]); # [apple, banana]
    print(fruits[-1]); # cherry

    # dictionary
    person = {
        "name": "Alice",
        "age": 25,
        "city": "Seattle"
    };

    # Access values by key
    print(person["name"]);  # Alice
    print(person["age"]);   # 25

    # tuples are immutable lists
    point = (10,20);
    print(point[0]);  # 10
    print(point[1]);  # 20

    # tuples can be unpacked with parentheses
    (x, y) = point;
    print(f"x={x}, y={y}");

    # list comprehensions
    squares = [i ** 2 for i in range(5)];
    print(squares);  # [0, 1, 4, 9, 16]

    # With condition
    evens = [i for i in range(10) if i % 2 == 0];
    print(evens);  # [0, 2, 4, 6, 8]

    learnClasses();
    learnOSP();
}

def learnClasses() {

    # the class keyword follows default Python behavior
    # all members are static
    class Cat {
        has name: str = "Unnamed";
        def meow {
            print(f"{self.name} says meow!");
        }
    }

    your_cat = Cat();
    my_cat = Cat();
    my_cat.name = "Shrodinger";

    my_cat.meow();   # Shrodinger says meow!
    your_cat.meow(); # Shrodinger says meow!

    # the obj keyword follows the behavior of Python dataclasses
    # all members are per-instance
    obj Dog {
        has name: str = "Unnamed";
        has age: int = 0;

        def bark {
            print(f"{self.name} says Woof!");
        }
    }
    your_dog = Dog();
    my_dog = Dog();
    my_dog.name = "Buddy";
    my_dog.age = 3;

    your_dog.bark(); # Unnamed says Woof!
    my_dog.bark();   # Buddy says Woof!

    # inheritance
    obj Puppy(Dog){
        has parent: str = 0;
        def bark { # override
            print(f"Child of {self.parent} says Woof!");
        }
    }
}

# Jac also supports graph relationships within the type system
# This is called Object Spatial Programming

# nodes are objs with special properties
node Person {
    has name: str;
    has age: int;
}

def learnOSP(){
    a = Person(name="Alice",age=25);
    b = Person(name="Bob",age=30);
    c = Person(name="Charlie",age=28);

    # connection operators create edges between nodes
    a ++>  b; # forward a->b
    b <++  c; # backward c->b
    a <++> c; # bidirectional a <-> c

    # edges can be typed, providing additional meaning
    edge Friend {
        has since: int;
    }

    a +>:Friend(since=2020):+> b;
    a +>:Friend(since=1995):+> c;


    # edges and nodes can be queried with filters

    # returns all outgoing nodes with friend edges since 2018
    old_friend_nodes = [node a ->:Friend:since > 2018:->];

    # returns all outgoing friend edges since 2018
    old_friend_edges = [edge a->:Friend:since > 2017:->];

    # Walkers are objects that "walk" across nodes doing operations
    # Walkers contain automatic methods that trigger on events
    # These methods are called abilities
    walker Visitor {
        has name: str;

        # abilities follow can <name> with <type> <operation> syntax
        # runs when walker spawns at root
        can start with `root entry {
            print(f"Starting!");
            # visit moves to an adjacent node
            visit [-->]; # [-->] corresponds to outgoing connections
            # visit [<--]; incoming connections
            # visit [<-->]; all connections
        }

        # runs when walker visits any person
        can meet_person with Person entry {
            # here refers to current node
            # self refers to walker
            print(f"Visiting {here.name} with walker {self.name}");
            if here.name == "Joe" {
                print("Found Joe");
                disengage; # stop traversal immediately
            }

            # report returns a value without stopping exeuction
            # all reported values are accessed as a list after traversal
            report here.name;
            visit [-->];
        }

        # runs when walker is done
        can finish with exit {
            print("Ending!");
        }
    }

    # nodes can also have abilities
    node FriendlyPerson(Person) {
        has name:str;
        can greet with Visitor entry{
            print(f"Welcome, visitor");
        }
    }

    f = FriendlyPerson(name="Joe",age=10);

    # root is a special named node in all graphs
    root ++> f ++> a;

    # walker can then be spawned at a node in the graph
    root spawn Visitor("Jim");
}

def learnSpecial(){
    # lambdas create anonymous functions
    add = lambda a: int, b: int -> int : a + b;
    print(add(5, 3));

    # walrus operator allow assignment within expressions
    result = (y := 20) + 10;
    print(f"y = {y}, result = {result}");

    # flow/wait is jac's equivalent to async/await for concurrency
    # in jac, these are executed in a thread pool
    def compute(x: int, y: int) -> int {
        print(f"Computing {x} + {y}");
        return x + y;
    }

    def slow_task(n: int) -> int {
        print(f"Task {n} started");
        time.sleep(1);
        print(f"Task {n} done");
        return n * 2;
    }

    task1 = flow slow_task(42);
    task2 = flow compute(5, 10);
    task3 = flow compute(3, 7);

    result1 = wait task1;
    result2 = wait task2;
    result3 = wait task3;
    print(f"Results: {result1}, {result2}, {result3}");
    #* Output:
    Task 42 started
    Computing 5 + 10
    Computing 3 + 7
    Task 42 done
    Results: 84, 15, 10
    *#
}

# all programs start from the entry node
with entry {
    # print function outputs a line to stdout
    print("Hello world!");

    # call some other function
    nextFunction();
}