Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,4 @@ set_property(TEST frozen_bridge_segfault.frank PROPERTY WILL_FAIL true)
set_property(TEST unresolved_field.frank PROPERTY WILL_FAIL true)
set_property(TEST unresolved_global.frank PROPERTY WILL_FAIL true)
set_property(TEST unresolved_name.frank PROPERTY WILL_FAIL true)
set_property(TEST func_no_return.frank PROPERTY WILL_FAIL true)
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ model inspired by [behaviour-oriented concurrency](https://doi.org/10.1145/36228
To this end, FrankenScript programs generate a file called `mermaid.md` with a Mermaid diagram per line in the source program showing the object and region
graph of the program at that program point.

This is a legend for the diagrams that FrankenScript generates:

![](./docs/frankenscript-legend.png)

## Pre-requisites

This project is C++20 based and uses CMake as the build system. We also recommend installing Ninja to speed up the build process.
Expand Down Expand Up @@ -49,3 +53,4 @@ You can run in interactive mode by running:
```

Which will keep overwritting the `mermaid.md` file with the new heap state after each step.

Binary file added docs/frankenscript-legend.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 43 additions & 0 deletions examples/01-objects.frank
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# This file shows how different objects can be created and used in FrankenScript
#
# The following line creates a dictionary and assigns it to the variable `a`.
# The variable name is on the arrow from the frame to the object. The object
# displays the memory address and the reference count of the object.
a = {}

# Fields of dictionaries can be assigned in two ways:
a["field1"] = {} # 1
a.field2 = {} # 2

# Fields can be accessed the same way
b = a.field1
c = a["field1"]

# FrankenScipt uses reference counting. Values are deallocated when the reference
# count hits 0. This can be used to remove unused elements from the diagram.
a = None
b = None
c = None

# FrankenScript also provides stings, created by double quotes:
d = "This is a string"

# All objects in FrankenScript are dictionaries. The language uses prototypes
# to share functionality across objects and to identify the type of object.
# The diagram currently shows the prototype for frame objects (called `[Frame]),
# and the prototype for strings (called `[String]`).
#
# Prototypes can be accessed via the `__proto__` field.
e = d.__proto__

# The prototype can also be written to. This creates an object `g` with the
# prototype `f`
f = {}
f.field = {}
g = {}
g.__proto__ = f

# If a field is not found on an object the prototype will be checked for the
# field. In this example, a reference to `f.field` is returned since `g` doesn't
# have a field called `field` but the prototype has one.
h = g.field
31 changes: 31 additions & 0 deletions examples/02-freezing.frank
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Freezing describes the action of making an object immutable.
#
# Objects in FrankenScript are mutable by default.
x = {}

# Immutable objects are ice blue and not contained by a specific region
freeze(x)

# Assigning to a field of `x` afterward will throw an error
# x.field = {} # ERROR

# Calling `freeze(x)` freezes the object `x` points to but not the variable
# itself, so x can be reassigned
x = {}
x.f = {} # Mutation of x

# Freezing is deep, meaning that all objects reachable from a
# frozen object will also be frozen. This is needed for safe sharing
# across concurrent readers. See §2.1 for more details
freeze(x)

# The immutable status in Lungfish refers to the observable state.
# This means that the reference count can remain mutable as long as
# it is not observable in the program. Notice how this new reference
# increases the reference count. See §5.3.5 for more details.
xx = x

# Setting all references to an immutable object to `None`
# will deallocate it.
xx = None
x = None
50 changes: 50 additions & 0 deletions examples/03-regions1.frank
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Lungfish uses regions to track and enforce ownership dynamically.
# The previous examples only worked in the local region drawn in light
# green. This example shows how new regions can be created and used.
#
# The `Region()` constructor creates a new region and returns its bridge
# object. The new region is drawn in yellow. Any object in the yellow
# rectangle belongs to the region. The trapezoid shape denotes the
# bridge object. It displays the following information about the region:
# - LRC: The number of incoming references from the local region. This
# counter tracks references to all objects in the region not
# just the bridge object. See §3.1 for more details.
# - SBRC: The number of open subregions.
# - RC: The reference count of the bridge object.
r = Region()

# Any objects reachable from the bridge or an object in the region
# will automatically be part of the region. Notice how the new dictionaries
# are members of the region, indicated by them being in the same yellow box.
r.field = {}
r.field.data = {}

# Objects inside a region have no topological restrictions. As such we can create
# cycles, like this:
r.field.bridge = r
r.field.data.parent = r.field

# All objects are by created in the local region.
x = {}
x.data = {}

# An object in the local region is moved into a region, when an object in the
# region references it. All reachable objects from the moved object are also
# moved into the region. Figure 7 in the paper shows the individual steps of
# this process.
r.x = x

# Moving the value of `x` into the region increased the LRC since the variable `x`
# is a reference from the local frame into the region. Reassigning `x` to another
# value will decrement the LRC again. This is done by a write barrier, discussed in
# §3.2 of the paper.
x = None

# References to frozen objects are unrestricted. Table 1 in the paper provides a
# good overview of what references are allowed.
r.x.data = None

# When a region has no incoming references it and all contained objects can
# be deallocated as a unit. This even allows the collection of topologies
# with cycles.
r = None
52 changes: 52 additions & 0 deletions examples/04-regions2.frank
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# 03-regions1.frank covered how new regions can be created. This
# example covers how ownership is enforced with regions.
r1 = Region()
r1.data = {}
r2 = Region()

# Region 1 owns an object with the name `data`. All objects can at most have
# one owner. Creating a reference from one region to an object in another region
# will result in an error
# r2.data = r1.data # Error

# However, creating a single reference to a bridge object of another region
# is allowed. This reference is called the owning reference, indicated by
# the solid orange arrow. The region of the referenced bridge object is now
# a subregion of the first region.
r1.data.r2 = r2

# Attempts to add additional owning references will result in an error:
# r1.new = r2 # Error

# Regions are arranged in a forest. It is not allowed to create a cycle
# between regions. Attempting to make `r1` a subregion of `r2` which is
# currently a subregion of `r1` will result in an error:
# r2.r1 = r1 # Error

# Region 2 is currently considered open as it has an incoming reference from
# the local region. This is indicated by the LRC not being zero. The parent
# region `r1` has a SBRC of 1 indicating that a subregion of it is open.
#
# Removing the local reference into `r2` will close the region and also decrement
# the SBRC of the parent region.
r2 = None

# Creating a reference into the subregion will open it again.
r2 = r1.data.r2

# Subregions can be unparented again by overwriting the owning reference.
# This is possible since there can only be one owing reference at a time.
r1.data.r2 = None

# Region 2 can now take ownership of `r1`.
r2.r1 = r1

# Regions can also be frozen to allow multiple regions to reference them.
# The bridge object of the frozen region will remain, but it will lose the
# `[RegionObject]` prototype.
freeze(r1)

# The previous bridge object referenced by `r1` can now be referenced by other
# regions.
r3 = Region()
r3.r1 = r1
42 changes: 42 additions & 0 deletions examples/05-regions3.frank
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# 04-regions2.frank covered how ownership is enfored with regions.
# This example covers some built-in functions for regions.

r1 = Region()
r1.data1 = {}
r1.data2 = {}

# The `is_closed()` function can be used to check if a region
# is open. Region 1 is currently considered open since it has
# an incoming reference from the local region.
res = is_closed(r1)

# Region 2 is created to be the new owner of Region 1.
r2 = Region()
r2.child = r1

# The `close()` function can be used to set all incoming local
# references to None.
close(r1)

# We can also check the status with `is_closed`.
res = is_closed(r2.child)

# A subregion can be merged into its parent region, with the
# `merge()` function. The bridge object of the subregion loses
# the `[RegionObject]` prototype, since it is no longer the bridge
# object of a region.
merge(r2.child, r2)

# Regions can also be dissolved, thereby moving all contained
# objects back into the local region.
#
# This function differs syntactically from the paper which
# uses `merge(r2)`.
dissolve(r2)

# This can be used to reconstruct regions. This example uses
# a for loop:
r1 = Region()
for key, value in r2.child:
r1[key] = value
r2.child = r1
53 changes: 53 additions & 0 deletions examples/06-cowns.frank
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# This is the sixth example and assumes the knowledge of the
# previous examples.
#
# Lungfish uses concurrent owners, called Cowns, to coordinate
# concurrent access at runtime. Cowns and their states are
# introduced in §2.3.1 of the paper.
#
# Cowns can store immutable objects, regions and other cowns.
# The following will create a cown pointing to the built-in
# frozen value `None`.
c1 = Cown(None)

# The cown has the status "Released" which means that a concurrent
# unit can aquire it and access its data. Attempting to access
# the data in this state will result in an error.
# ref = c1.value # Error

# Regions are used to track ownership of mutable data. This can be
# combined with cowns to safely share data. This created an open region:
r1 = Region()
r1.data = {}
data = r1.data

# A cown created from an open region has the status "Pending Release".
# This status allows access to the contained value. However, it will
# switch to the "Released" status when the region is closed
c2 = Cown(r1)
close(c2.value)

# Cowns can be safely shared across threads since they dynamically enforce
# ownership and concurrent coordination at runtime. They are therefore allowed
# to be referenced by frozen objects. The freeze will not effect the cown
# or the contained values.
x = {}
x.cown = c2
freeze(x)

# Cowns can also be referenced from one or multiple regions like this:
r2 = Region()
r2.cown = c2
r3 = Region()
r3.cown = c2

# Regions referencing a cown can still be closed and sent. This example
# uses the `move` keyword to perform a destructive read and close the
# region as part of the cown creation. The cown is therefore created in
# the "Released" status
c3 = Cown(move r2)

# Cowns can also point to other cowns. The second cown is also
# in the "Released" state since the given cown is allowed to
# have local references.
c4 = Cown(c2)
63 changes: 63 additions & 0 deletions examples/07-expressions.frank
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# The previous examples introduced objects, freezing, regions and cowns.
# This example is a showcase of most expressions that FrankenScript
# supports. It can be used as a reference for writing your own scripts.
#
# A list of built-in function can be found in the `docs` folder. Here is
# a link to the rendered version on GitHub:
# <https://github.com/fxpl/frankenscript/blob/main/docs/builtin.md>

# This is how you construct the objects discussed in example 01
a = {}
x = "a new string"
x = Region()
x = Cown(None)

# FrankenScript has a few built-in and frozen objects:
x = True # The boolean value `true`
x = False # The boolean value `false`
x = None # The null value, similar to Python's None

# FrankenScript supports functions with arguments and return values.
def id(x):
return x

# A function can be called like this. Each function call adds a new frame
# to the diagram. The frame holds all variables known to the current scope.
# The frame is deleted when the function returns.
id(a)

# Function objects are hidden from the mermaid to make it cleaner. But they're
# still normal objects that can be used in assignments like this:
a.id = id

# This can be used to simulate method calls. Calling a function on stored in a
# field will pass the object in as the first argument.
a = a.id() # a is passed in as the first argument

# The move keyword can be used for destructive reads. The previous location
# is reassigned to None.
b = move a

# FrankenScript has two comparison operators. These check for object identity:
res = b == None
res = b != None

# Boolean values can be used in if statements:
if res:
pass() # A built-in function for empty blocks
else:
unreachable() # A built-in function for unreachable branches

# The else block is optional:
if res:
pass()

# Boolean expressions can also be used in while loops:
while res == True:
res = False

# For loops can be used to iterate over all fields of an object.
a = {}
a.field = "value"
for key, value in b:
pass()
Loading