Welcome to the official reference guide for the Jac programming language. This document is designed to serve as a comprehensive reference manual as well as a formal specification of the language. The mission of this guide is to be a resource for developers seeking to answer the question, "How do I code X in Jac?"
This document is organized around the formal grammar for the language code examples and corresponding grammar snippets being directly generated from the actual grammar and test cases maintained in the official repository. We expect the descriptions may occasionally lag behind the rapid evolution of Jac in the early days. If you notice something, make a pull request and join our contributor community.
"""A Docstring can be added the head of any module.Any element in the module can also have a docstring.If there is only one docstring before the first element,it is assumed to be a module docstring.""""""A docstring for add function"""canadd(a:int,b:int)->int{returna+b;}# No docstring for subtract functioncansubtract(a:int,b:int)->int{returna-b;}withentry:__main__{print(add(1,subtract(3,1)));}
"""A Docstring can be added the head of any module.Any element in the module can also have a docstring.If there is only one docstring before the first element,it is assumed to be a module docstring.""""""A docstring for add function"""defadd(a:int,b:int)->int:returna+bdefsubtract(a:int,b:int)->int:returna-bif__name__=="__main__":print(add(1,subtract(3,1)))
start:modulemodule:(doc_tag? element (element_with_doc | element)*)?| doc_tag (element_with_doc (element_with_doc | element)*)?doc_tag:STRING | DOC_STRINGelement_with_doc:doc_tag elementelement:import_stmt| architype| ability| global_var| free_code| py_code_block| test
Description
In Jac, a module is analogous to a Python module, serving as a container for various elements such as functions, classes (referred to as "architypes" later in this document), global variables, and other constructs that facilitate code organization and reusability. Each module begins with an optional module-level docstring, which provides a high-level overview of the module's purpose and functionality. This docstring, if present, is positioned at the very start of the module, before any other elements.
Docstrings
Jac adopts a stricter approach to docstring usage compared to Python. It mandates the inclusion of a single docstring at the module level and permits individual docstrings for each element within the module. This ensures that both the module itself and its constituent elements are adequately documented. If only one docstring precedes the first element, it is automatically designated as the module-level docstring.
Also Note, that Jac enforces type annotations in function signatures and class fields to promote type safety and ultimately more readable and scalable codebases.
Elements within a Jac module encompass familiar constructs from Python, including functions and classes, with the addition of some unique elements that will be discussed in further detail. Below is a table of module elements in Jac. These constructs are described in detail later in this document.
Includes traditional python class construct with equiviant semantics, and additionaly introduces a number of new class-like constructs including obj, node, edge, and walker to enable the data spatial programming paradigmn
Construct (with entry {...}) to express presence of free floating code within a module that is not part of a function or class-like object. Primarily for code cleanliness, readability, and maintainability.
Native python code can be inlined alongside jac code at arbitrary locations in a Jac program using ::py:: directive
Moreover, Jac requires that any standalone, module-level code be encapsulated within a with entry {} block. This design choice aims to enhance the clarity and cleanliness of Jac codebase.
Jac's import and include statements provides a propoer superset of python's import semantics with some improvements for imports of Jac modules. That being said, its important to note an important syntax difference in that from X import Y is reworked into import from X, Y (import X remains in the style of import X in Jac). Also there is a :py or :jac after the import statment to signify which type of import to consider.
Python Imports: Utilize import:py to seamlessly import Python libraries, allowing full access to Python's ecosystem within Jac programs, with the same semantics as python.
Jac Imports: Use import:jac for importing Jac-specific modules from .jac files with the same symantics as python files with and additional functionality for Jac's impl separation (described later), and static analysis features.
canprint_base_classes(cls:type)->type{print(f"Base classes of {cls.__name__}: {[c.__name__forcincls.__bases__]}");returncls;}classAnimal{}objDomesticated{}@print_base_classesnodePet:Animal,Domesticated:{}walkerPerson:Animal:{}walkerFeeder:Person:{}@print_base_classeswalkerZoologist:Feeder:{}
fromjaclang.plugin.featureimportJacFeatureasjacdefprint_base_classes(cls:type)->type:print(f"Base classes of {cls.__name__}: {[c.__name__forcincls.__bases__]}")returncls@jac.make_obj(on_entry=[],on_exit=[])classAnimal:pass@jac.make_obj(on_entry=[],on_exit=[])classDomesticated(jac.Obj):pass@print_base_classes@jac.make_node(on_entry=[],on_exit=[])classPet(Animal,Domesticated,jac.Node):pass@jac.make_walker(on_entry=[],on_exit=[])classPerson(Animal,jac.Walker):pass@jac.make_walker(on_entry=[],on_exit=[])classFeeder(Person,jac.Walker):pass@print_base_classes@jac.make_walker(on_entry=[],on_exit=[])classZoologist(Feeder,jac.Walker):pass
The provided Jac code snippet demonstrates the use of archetypes (a key concept in Jac programming), which supersets traditional classes in object-oriented programming. In Jac, archetypes can be defined using different keywords to signify their roles and characteristics within the program. These include obj, node, walker, edge, and class, each serving distinct purposes in the design of a Jac application.
Defining Archetypes
Object (obj): This keyword is used to define a basic archetype. In the example, Animal and Domesticated are simple archetypes with no inherited or additional members.
Node (node): Defines an archetype that can be part of a graph object structure. Pet is declared as a node and inherits features from Animal and Domesticated, demonstrating multiple inheritance.
Walker (walker): This keyword is used to define archetypes that perform actions or traverse nodes and edges within a graph. Person, Feeder, and Zoologist are examples of walkers, with Zoologist also including the use of a decorator.
Inheritance
The example illustrates how archetypes can inherit from one or more other archetypes, using the syntax :ParentArchetype:. For instance, Feeder inherits from Person, which in turn inherits from Pet, indicating a chain of inheritance.
Decorators
Decorators in Jac, denoted by @, are used to modify or enhance the behavior of archetypes without altering their code directly. The @print_base_classes decorator is applied to Pet and Zoologist to print their base classes at runtime, demonstrating a practical use of decorators for introspection or debugging purposes.
classCar:wheels:int=4def__init__(self,make:str,model:str,year:int):self.make=makeself.model=modelself.year=yeardefdisplay_car_info(self):print(f"Car Info: {self.year}{self.make}{self.model}")@staticmethoddefget_wheels():returnCar.wheelscar1=Car("Toyota","Camry",2020)car1.display_car_info()print("Number of wheels:",Car.get_wheels())
In Jac, an architype functions similarly to classes in traditional object-oriented programming languages. It allows the definition of data structures with both state and behavior encapsulated within. The provided Jac code snippet demonstrates the definition and usage of an architype named Car.
The Car architype includes three instance variables (make, model, and year) and one class variable (wheels). Instance variables are defined using the has keyword and can have specific types (str for strings and int for integers). The static keyword precedes the class variable definition, indicating that wheels is shared across all instances of Car.
Types are annotated directly after the variable name (required for all has variables) and are followed by a colon. For instance, make: str declares a variable make of type string.
The Car architype also defines two methods, display_car_info as an instance method and get_wheels as a static method. Methods are introduced with the can keyword. The instance method display_car_info uses a formatted string to print car information, accessing instance variables with the self reference. The static method get_wheels returns the value of the static variable wheels.
An instance of Car is created using the architype name as a constructor, passing in values for make, model, and year. This instance is assigned to the variable car.
Instance methods are invoked using the dot notation on the instance (car.display_car_info()), and static methods are called directly on the architype (Car.get_wheels()).
The entry block serves as the entry point of the program, where a Car instance is created, and its methods are invoked to display the car's information and the number of wheels.
fromenumimportEnum,auto,unique@uniqueclassColor(Enum):RED=1pencil=auto()classRole(Enum):ADMIN=("admin",)USER="user"print("Initializing role system..")deffoo():return"Accessing privileged Data"print(Color.RED.value,Role.foo())
Jac has enumerations directly in the language. The enum Color defines an enumeration called Color, which includes two members: RED assigned the value 1 and pencil which implicitly takes the next integer value, 2 (effectively the same as a typical pencil = auto() in Python's enum).
The entry point of the program is defined with with entry, which serves as the main function or starting point for execution. Inside this block, the code prints the value of the RED enumeration member using print(Color.RED.value);. Since RED is assigned the value 1, this statement will output 1 to the console when the program runs.
objDivider{candivide(x:float,y:float)->float{return(x/y);}}#this is an abstract class as it has the abstract methodobjCalculator{static can:privmultiply(a:float,b:float)->float{returna*b;}cansubstract->floatabs;canadd(number:float,*a:tuple)->float;}objsubstractor:Calculator:{cansubstract(x:float,y:float)->float{return(x-y);}}:obj:Calculator:can:add(number:float,*a:tuple)->float{return(number*sum(a));}withentry{div=Divider();sub=substractor();print(div.divide(55,11));print(Calculator.multiply(9,-2));print(sub.add(5,20,34,56));print(sub.substract(9,-2));}
objPoint{hasx:float,y:float;}canmatch_example(data:any){matchdata{# MatchValuecase42:print("Matched the value 42.");# MatchSingletoncaseTrue:print("Matched the singleton True.");caseNone:print("Matched the singleton None.");# MatchSequencecase[1,2,3]:print("Matched a specific sequence [1, 2, 3].");# MatchStarcase[1,*rest,3]:print(f"Matched a sequence starting with 1 and ending with 3. Middle: {rest}");# MatchMappingcase{"key1":1,"key2":2,**rest}:print(f"Matched a mapping with key1 and key2. Rest: {rest}");# MatchClasscasePoint(int(a),y=0):print(f"Point with x={a} and y=0");# MatchAscase[1,2,rest_valasvalue]:print(f"Matched a sequence and captured the last value: {value}");# MatchOrcase[1,2]|[3,4]:print("Matched either the sequence [1, 2] or [3, 4].");case_:print("No match found.");}}withentry{match_example(Point(x=9,y=0));}
classPoint:def__init__(self,x:float,y:float):self.x=xself.y=ydefmatch_example(data:any):matchdata:# MatchValuecase42:print("Matched the value 42.")# MatchSingletoncaseTrue:print("Matched the singleton True.")caseNone:print("Matched the singleton None.")# MatchSequencecase[1,2,3]:print("Matched a specific sequence [1, 2, 3].")# MatchStarcase[1,*rest,3]:print(f"Matched a sequence starting with 1 and ending with 3. Middle: {rest}")# MatchMappingcase{"key1":1,"key2":2,**rest}:print(f"Matched a mapping with key1 and key2. Rest: {rest}")# MatchClasscasePoint(x=int(a),y=0):print(f"Point with x={a} and y=0")# MatchAscase[1,2,rest_valasvalue]:print(f"Matched a sequence and captured the last value: {value}")# MatchOrcase[1,2]|[3,4]:print("Matched either the sequence [1, 2] or [3, 4].")case_:print("No match found.")match_example(Point(x=9,y=0))
objPoint{hasx:float,y:float;}withentry{data=Point(x=9,y=0);matchdata{casePoint(int(a),y=0):print(f"Point with x={a} and y=0");case_:print("Not on the x-axis");}}
from__future__importannotationsfromjaclang.plugin.featureimportJacFeatureasJacfromdataclassesimportdataclassasdataclass@Jac.make_obj(on_entry=[],on_exit=[])@dataclass(eq=False)classPoint:x:floaty:floatdata=Point(x=9,y=0)matchdata:casePoint(int(a),y=0):print(f"Point with x={a} and y=0")case_:print("Not on the x-axis")
globx="Jaclang ";canfoo()->None{:g:x;x='Jaclang is ';y='Awesome';canfoo2()->tuple[str,str]{:nl:y;y="Fantastic";return(x,y);}print(x,y);print(foo2());}withentry{foo();}
from__future__importannotationsx="Jaclang "deffoo()->None:globalxx="Jaclang is "y="Awesome"deffoo2()->tuple[str,str]:nonlocalyy="Fantastic"return(x,y)print(x,y)print(foo2())foo()
walkerProducer{canproducewith`rootentry;}nodeProduct{hasnumber:int;canmakewithProducerentry;}:walker:Producer:can:produce{end=here;fori=0toi<=2byi+=1{end++>(end:=Product(number=i+1));}visit[-->];}:node:Product:can:make->str{print(f"Hi, I am {self} returning a String");visit[-->];}withentry{rootspawnProducer();}
from__future__importannotationsfromjaclang.plugin.featureimportJacFeatureasJacfromjaclang.plugin.builtinimport*fromdataclassesimportdataclass@Jac.make_walker(on_entry=[Jac.DSFunc("produce")],on_exit=[])@dataclass(eq=False)classProducer(Jac.Walker):defproduce(self,_jac_here_:Jac.RootType)->None:end=_jac_here_i=0whilei<=2:Jac.connect(left=end,right=(end:=Product(number=i+1)),edge_spec=Jac.build_edge(is_undirected=False,conn_type=None,conn_assign=None),)i+=1ifJac.visit_node(self,Jac.edge_ref(_jac_here_,target_obj=None,dir=Jac.EdgeDir.OUT,filter_func=None,edges_only=False,),):pass@Jac.make_node(on_entry=[Jac.DSFunc("make")],on_exit=[])@dataclass(eq=False)classProduct(Jac.Node):number:intdefmake(self,_jac_here_:Producer)->None:print(f"Hi, I am {self} returning a String")ifJac.visit_node(_jac_here_,Jac.edge_ref(self,target_obj=None,dir=Jac.EdgeDir.OUT,filter_func=None,edges_only=False,),):passJac.spawn_call(Jac.get_root(),Producer())
withentry{foriinrange(9){ifi>2{print("loop is stopped!!");break;}print(i);}foriin"WIN"{ifi=="W"{continue;}print(i);}#skip is not working need to look at it}
from__future__importannotationsfromjaclang.plugin.featureimportJacFeatureas_Jac@_Jac.make_walker(on_entry=[_Jac.DSFunc("self_destruct",None)],on_exit=[])classVisitor:defself_destruct(self,_jac_here_)->None:print("get's here")_Jac.disengage(self)returnprint("but not here")_Jac.spawn_call(_Jac.get_root(),Visitor())
withentry{if5>4{print("True");}elif"a"!="b"{print("'a' is 'a' ");}else{print("No");}a=[1,2,3];b=[1,2,3];print(aisb);print(3ina);print(TrueorFalse);print(FalseandFalse);}
walkerAdder{candowith`rootentry;}nodenode_a{hasx:int=0,y:int=0;canaddwithAdderentry;}:walker:Adder:can:do{here++>node_a();visit[-->];}:node:node_a:can:add{self.x=550;self.y=450;print(int(self.x)+int(self.y));}withentry{# spawn will iniiate the walker Adder from root nodeAdder()spawnroot;}
cancombine_via_func(a:int,b:int,c:int,d:int)->int{returna+b+c+d;}withentry{first_list=[1,2,3,4,5];second_list=[5,8,7,6,9];combined_list=[*first_list,*second_list];print(combined_list);# Original dictionaryfirst_dict={'a':1,'b':2};# Another dictionarysecond_dict={'c':3,'d':4};# Combining dictionaries using dictionary unpackingcombined_dict={**first_dict,**second_dict};# Printing the combined dictionaryprint(combine_via_func(**combined_dict));print(combine_via_func(**first_dict,**second_dict));}
classX:a_b=67y="aaa"+f"b{a_b}bbcc"c=(3,4,5)l_1=[2,3,4,5]defentry():x=Xa="abcde...."b=Truec=bin(12)d=hex(78)# e = 0x4Eprint(l_1,a,b,c,d)print(x.y)# Run the entry blockentry()
withentry{squares={num:num**2fornuminrange(1,6)};even_squares_set={num**2fornuminrange(1,11)ifnum%2==0};squares_generator=(num**2fornuminrange(1,6));squares_list=[num**2fornuminsquares_generator];# BREAKS: squares_list = [num**2 for num in squares_generator if num != 9];# TODO: Issure with '\n' below, its being escapedprint("\n".join([str(squares),str(even_squares_set),str(squares_list)]));print({"a":"b","c":"d"},# Dictionary value{"a"},# Set value("a",),# Tuple value['a']);# List value}
squares={num:num**2fornuminrange(1,6)}even_squares_set={num**2fornuminrange(1,11)ifnum%2==0}squares_generator=(num**2fornuminrange(1,6))squares_list=[num**2fornuminsquares_generator]print("\n".join([str(squares),str(even_squares_set),str(squares_list)]))print({"a":"b","c":"d"},# Dictionary value{"a"},# Set value("a",),# Tuple value["a"],# List value)
walkerCreator{cancreatewith`rootentry;}nodenode_a{hasval:int;canmake_somethingwithCreatorentry;}edgeconnector{hasvalue:int=10;}:walker:Creator:can:create{end=here;fori=0toi<3byi+=1{end++>(end:=node_a(val=i));}end+:connector:value=i:+>(end:=node_a(val=i+10));root<+:connector:value=i:+(end:=node_a(val=i+10));visit[-->];}:node:node_a:can:make_something{i=0;whilei<5{print(f"wlecome to {self}");i+=1;}}withentry{rootspawnCreator();}
from__future__importannotationsfromjaclang.plugin.featureimportJacFeatureasJacfromjaclang.plugin.builtinimport*fromdataclassesimportdataclass@Jac.make_walker(on_entry=[Jac.DSFunc("create")],on_exit=[])@dataclass(eq=False)classCreator(Jac.Walker):defcreate(self,_jac_here_:Jac.RootType)->None:end=_jac_here_i=0whilei<3:Jac.connect(left=end,right=(end:=node_a(val=i)),edge_spec=Jac.build_edge(is_undirected=False,conn_type=None,conn_assign=None),)i+=1Jac.connect(left=end,right=(end:=node_a(val=i+10)),edge_spec=Jac.build_edge(is_undirected=False,conn_type=connector,conn_assign=(("value",),(i,))),)Jac.connect(left=(end:=node_a(val=i+10)),right=Jac.get_root(),edge_spec=Jac.build_edge(is_undirected=False,conn_type=connector,conn_assign=(("value",),(i,))),)ifJac.visit_node(self,Jac.edge_ref(_jac_here_,target_obj=None,dir=Jac.EdgeDir.OUT,filter_func=None,edges_only=False,),):pass@Jac.make_node(on_entry=[Jac.DSFunc("make_something")],on_exit=[])@dataclass(eq=False)classnode_a(Jac.Node):val:intdefmake_something(self,_jac_here_:Creator)->None:i=0whilei<5:print(f"wlecome to {self}")i+=1@Jac.make_edge(on_entry=[],on_exit=[])@dataclass(eq=False)classconnector(Jac.Edge):value:int=Jac.has_instance_default(gen_func=lambda:10)Jac.spawn_call(Jac.get_root(),Creator())
#Filter comprehensionimport:py random;objTestObj{hasx:int=random.randint(0,15),y:int=random.randint(0,15),z:int=random.randint(0,15);}withentry{random.seed(42);apple=[];fori=0toi<10byi+=1{apple.append(TestObj());}#To get the instances with the y value less than 7print(apple(?y<=7));}#assign comprehensionobjMyObj{hasapple:int=0,banana:int=0;}withentry{x=MyObj();y=MyObj();mvar=[x,y](=apple=5,banana=7);print(mvar);}
objAnimal{hasspecies:str;hassound:str;}objDog:Animal:{hasbreed:str;hastrick:strbypostinit;canpostinit{self.trick="Roll over";}}objCat:Animal:{caninit(fur_color:str){super.init(species="Cat",sound="Meow!");self.fur_color=fur_color;}}withentry{dog=Dog(breed="Labrador",species="Dog",sound="Woof!");cat=Cat(fur_color="Tabby");print(dog.breed,dog.sound,dog.trick);# print(f"The dog is a {dog.breed} and says '{dog.sound}'");# print(f"The cat's fur color is {cat.fur_color}");}
fromdataclassesimportdataclass,field@dataclassclassAnimal:species:strsound:str@dataclassclassDog(Animal):breed:strtrick:str=field(init=False)def__post_init__(self):self.trick="Roll over"@dataclassclassCat(Animal):def__init__(self,fur_color:str):super().__init__(species="Cat",sound="Meow!")self.fur_color=fur_colordog=Dog(breed="Labrador",species="Dog",sound="Woof!")cat=Cat(fur_color="Tabby")print(dog.breed,dog.sound,dog.trick)# print(f'The dog is a {dog.breed} and says "{dog.sound}"')# print(f"The cat's fur color is {cat.fur_color}")
withentry{x="a";y=25;print(f"Hello {x}{y}{{This is an escaped curly brace}}");person={"name":"Jane","age":25};print(f"Hello, {person['name']}! You're {person['age']} years old.");print("This is the first line.\n This is the second line.");print("This will not print.\r This will be printed");print("This is \t tabbed.");print("Line 1\fLine 2");words=["Hello","World!","I","am","a","Jactastic!"];print(f"{'\n'.join(words)}");}
x="a"y=25print(f"Hello {x}{y}{{This is an escaped curly brace}}")person={"name":"Jane","age":25}print(f"Hello, {person['name']}! You're {person['age']} years old.")print("This is the first line.\n This is the second line.")print("This will not print.\r This will be printed")print("This is \t tabbed.")print("Line 1\x0cLine 2")words=["Hello","World!","I","am","a","Jactastic!"]print(f'''{"""""".join(words)}''')