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();
}