9.0.0.1

7 — Class: Semantics🔗

Due Thursday, 23 October 2025, 11:59:59pm

Purpose to build a model of the semantics of a language with a simple class system

Delivery Deliver your solutions in a directory called 7 within your assigned GitHub repository. The directory should contain the following items:

  • the executables xrun as specified below;

  • the sub-directory 7/Tests/ with the required integration test; and

  • an optional sub-directory called 7/Other/ for all auxiliary files.

If you solve the optional challenge task, report the solution in the pitch.md file and place this into the (7/) directory of your repo.

The directory 7/ may also contain a Makefile (see Make) in case you need us to build your executable. An executable should not be checked into a repository.

Programming Task Using your favorite programming language, you are to create a semantic model of the Class language, an extension of Core with a simple class system. This task requires a systematic adaptation of the CESK machine for Core.

Design a data representation for the states of the CESK machine for Class. The states are like those for the CESK machine for Core, except that all reference to a Core AST are re-directed to ASTs for Class.

Next, implement the necessary functionality to get the machine running:
  • load, which consumes an AST of a Class program (see 6 — Class: Syntax), constructs an initial CESK state, and makes the collection of class ASTs available to the transition function as a global constant;

  • transition, which consumes a non-final CESK state and constructs the next state as specified in figures 7 and 10; and

  • unload, which consumes a final CESK state and returns an answer: a number, an object representation, or an informative error message.

The CESK machine itself is a function from CLass ASTs to answers: it loads the program, loops the transition function, and unloads the final state.

The machine’s transition function for Class consists of two parts: a re-interpretation of figure 7 in the enlarged syntactic context and the addition of the cases listed in figure 10 concerning the creation, observation, and manipulation of objects. Concerning the re-interpretation, the key is to understand that in the case of Core, the machine knew of only one kind of value: numbers. In the context of Class, there are two kinds of values: numbers and objects. Hence all “instructions” of the transition function and auxiliary functions from 6 — Class: Syntax must account for this enlarged set of values.

The presentation of figure 10 makes the assumption that instances of classes in Class are represented as mutable values in the implementation language. While the machines presented so far are essentially mathematical functions—that is, the executable models do not rely on mutation in the implementing language—choosing mutable value for this assignment greatly simplifies the model.

In the following tables, < def*, stmt*, r > is notation for a stack whose top-most frame contains the block consisting of def*, a potentially empty sequence of definitions; stmt*, a potentially empty sequence of statements; and r, a return expressions (which may just be $). The notation f::others denotes a sequence with at least one element f, followed by a potentially empty sequences of others of the same kind.

Object Creation and Checking

 

Control  

 

Environment  

 

Store  

 

Kontinuation

evaluate a ‘new‘ expression (success)

before:

 

(new C x*)

 

e

 

s

 

k

 subject to:

 

class C has the same number of fields as x* has elements

after:

 

obj

 

e

 

s

 

k

 where

 

v*

 

=

 

s[e[ x* ]], ...

 and

 

obj

 

=

 

create instance of C using v* for the values of the fields

evaluate a ‘new‘ expression (failure)

before:

 

(new C x*)

 

e

 

s

 

k

 subject to:

 

class C does not have the same number of fields as x* has elements

after:

 

error

 

[ ]

 

[ ]

 

[ ]

evaluate an ‘isa‘ expression

before:

 

(o isa C)

 

e

 

s

 

k

after:

 

ans

 

e

 

s

 

k

 where

 

obj

 

=

 

s[e[ o ]]

 and

 

ans

 

=

 

check whether obj is instance of C

Field Access

 

Control  

 

Environment  

 

Store  

 

Kontinuation

evaluate a field reference (success)

before:

 

(o –> f)

 

e

 

s

 

k

 subject to:

 

o is an object and has the desired field

after:

 

ans

 

e

 

s

 

k

 where

 

obj

 

=

 

s[e[ o ]]

 and

 

ans

 

=

 

get value of f from obj

evaluate a field reference (failure)

before:

 

(o –> f)

 

e

 

s

 

k

 subject to:

 

o may not be an object or does not have the desired field

after:

 

error

 

[ ]

 

[ ]

 

[ ]

Method Call

 

Control  

 

Environment  

 

Store  

 

Kontinuation

evaluate a method-call expression (success)

before:

 

(o –> m (x* ...))

 

e

 

s

 

k

 subject to:

 

o is an object

 

 

... has the desired method

 

 

... the correct number of parameters

after:

 

 

e1

 

s1

 

push[ cl, k ]

 where

 

obj

 

=

 

s[e[ o ]]

 and

 

v*, ...

 

=

 

s[e[ x* ]], ...

 and

 

[ para*, body ]

 

=

 

the parameters & body of method m inthe class of obj

 and

 

cl

 

=

 

closure: body in e

 and

 

thisL

 

=

 

a new (relative to s) location

 and

 

xl*, ...

 

=

 

as many new (relative to s) locations as elements in x*

 and

 

e1

 

=

 

[ ][para* = xl*], ... [this = thisL]

 and

 

s1

 

=

 

s[xl* = v*], ... [thisL = obj]

evaluate a method-call expression (failure)

before:

 

(o –> m (x*))

 

e

 

s

 

k

 subject to:

 

o may not be an object or

 

 

... does not have the desired method

 

 

... takes a different number of arguments

after:

 

error

 

[ ]

 

[ ]

 

[ ]

Field Update

 

Control  

 

Environment  

 

Store  

 

Kontinuation

search ends a field assignment

before:

 

 

e

 

s

 

< [ ], (o –> f = rhs)::stmt*, r >

after:

 

rhs

 

e

 

s

 

< [ ], (o –> f = rhs)::stmt*, r >

execute a field assignment (success)

before:

 

v

 

e

 

s

 

< [ ], (o –> f = rhs)::stmt*, r >

 subject to:

 

o is an object and has the desired field

after:

 

 

e

 

s

 

< [ ], stmt*, r >

 where

 

obj

 

=

 

s[e[ o ]]

 and

 

 

 

set f in obj to v

execute a field assignment (failure)

before:

 

v

 

e

 

s

 

< [ ], (o –> f = rhs)::stmt*, r >

 subject to:

 

o may not be an object or does not have the desired field

after:

 

error

 

[ ]

 

[ ]

 

[ ]

Figure 10: The CESK semantics of the Class language: transition function

The xrun program reads one ExampleDD from STDIN, constructs the AST, performs the validity checks, runs the CESK machine, and writes one of the following strings to STDOUT: The outcomes are listed in the order in which they may occur for a given program.
  • "parser error" if the given ExampleDD is not an element of the Class grammar;

  • "duplicate class name" if in the well-formed Class program two of the class definitions come with the same name;

  • "duplicate method, field, or parameter name" if in the well-formed Class program one class definition comes with (at least) two fields or methods that have the same name;

  • "undeclared variable error" if in the well-formed Class program one of its variable occurrences comes without declaration or one occurrence of a ClassName in a statement or an expression comes without class definition;

  • "object" if the CESK machine produces an object for the well-formed and valid Class program;

  • a number n if the CESK machine produces a number n for the well-formed and valid Class system;

  • "run-time error" if the CESK machine produces an error outcome for the well-formed and valid Class program.

Challenge Eli Barzilay uses an AI at work. Recently, he wanted to know whether the AI understood the behavior of JS method and function calls. So he asked the AI. If you’re impatient, find the end of the thread and read just the last few interactions; it should peak your interest. In the middle, the two are essentially asking the philosophical questions that Kant tackled some 200+ years ago. The early parts show once again how utterly wrong the responses of an AI can be.

The program on the left has been fixed so that w calls this –> w() instead of the invalid other –> w().

((class While (count)

   (method w()

     (def d (this --> count))

     (def one -1.0)

     (def e (d + one))

     (if0 e

          (e = e)

          (this --> count = e))

     (this --> w ())))

 

 (def u 1000.0)

 (def w (new While (u)))

 

 (w --> w ()))

          

((class While ()

   (method w(other)

     (other --> w (this))))

 

 (class Repeat ()

   (method w(other)

     (other --> w (this))))

 

 (def r (new Repeat ()))

 (def w (new While ()))

 

 (w --> w(r)))

Figure 11: Examples of direct and indirect tail-calling methods

Figure 11 depicts two programs with tail-calling methods. Both programs will run forever if your implementation of the CESK machine is faithful to the specified transition function.

Your first task is to modify these programs so that they terminate without modifying their method call arrangement. That is, both programs should “iterate” many times after modification.

Your second task is to instrument your CESK machine so that it prints the size (depth) of the continuation stacK to the error device (STDERR) for every call to the state-transition function. With this instrumented machine, evaluate the programs in figure 11 and report the stack sizes for a few dozen “iterations.”

To get credit for this challenge problem, place the modified programs followed by the measurements into the pitch.md file. If you do so, also commit the instrumented transition function so that we can reproduce your measurements.

Note What you observe is that the CESK machine is a specification of Old language researchers speak of “tail call optimization,” but this phrase is utter nonsense. method calls that turns tail-calling methods into the equivalent of while loops. A space-faithful implementation of this specification is said to implement tail calls properly.

Bug and extra points The initial specification of PITCH is wrong and marked as such now. Extra points for a unit test that exposes the difference between the two. Point to this unit test in your code base for this homework with a perma-url at the top of pitch.md.

Testing Task Create 10 integration tests for xrun.

A test always consists of inputs, expected output, and an automated procedure for comparing the actual output with the expected one. — For this course, an integration test consists of a pair of files: n-in.ss, the input file, and n-out.ss, the expected output file, where n is an integer between 0 and the requested number of tests (exclusive). The comparison procedure is (string or numeric) equality on the content of the output file and the actual output. — Constraint No test file may exceed the size limit of 5Kb.

Readings Structured Data and Abstraction (CESK)