Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 strings, 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 affect 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