Our common framework

All of our languages will have a lot in common; for example,

  • common syntax

  • common pre-defined functions

  • (often) common behavior

Goal: eliminate superficial differences

  • Makes comparisons easy.

  • Differences that remain must be important!

Looking at languages

Three large conceptual domains for understanding the meaning of a programming language:

  • "syntax": what sequences of characters can be recognized as a program in the language; lexer and parser phases of a compiler.

  • "statics": what checks are performed before the program is executed (and, if the checks fail, the program is not executed); type checker phase of a compiler.

  • "dynamics": how does the program execute

Impcore

Imperative programming with an IMPerative CORE:

  • Has features found in most languages
    (loops and assignment; procedural abstraction)

  • Trivial syntax (from LISP)

  • No new language ideas (to introduce our common framework)

All of Impcore in one example

(val fact-ans 0)

(define fact (n)
  (if (< n 0)
    -1
    (begin
      (set fact-ans 1)
      (while (> n 0)
        (begin
          (set fact-ans (* fact-ans n))
          (set n (- n 1))))
      fact-ans)))

(print (fact 5))

Idea of LISP syntax

Parenthesized prefix syntax:

  • Names (e.g., variables) and integer literals are basic atoms.

  • Other constructs bracketed with parentheses.
    (Possible keyword after opening bracket)

Syntactic structure of Impcore

Two syntactic categories: definitions and expressions.

(Impcore has no statements!)

An Impcore program is a sequence of definitions.

Impcore definitions

  • (val x exp)

  • (define f (x …​) exp)

  • exp

Global variable definition

Example

(val n 99)

Compare

int n = 99;

Function definition

Example

(define mod (m n) (- m (* n (/ m n))))

Compare

int mod (int m, int n) {
  return m - n * (m / n);
}

Top-level expression definition

Example

(print (mod n 42))

Expressions at top level (as a definition) corresponds to an implicit definition of it.
The above is equivalent to

(val it (print (mod n 42)))

No comparable construct in C.

Impcore expressions

  • 99 (integer literal)

  • x (variable)

  • (if exp1 exp2 exp3)

  • (while exp1 exp2)

  • (set x exp)

  • (begin exp1 …​ expn)

  • (f exp1 …​ expn)

Impcore is expression-oriented:
Each one produces a value and may have side effects!

Functions are primitive (+ - * / = < > print)
or defined with (define f (x …​) exp).

The only type of data is "machine integer" (deliberate oversimplification).

Concrete syntax for Impcore

What we have seen above is concrete syntax: The form in which people write programs; that is, the sequence of characters in the source file.

Definitions and expressions:

def ::= (val x exp)
     |  (define f (x1 ... xn) exp)
     |  exp

exp ::= integer
     |  x
     |  (set x exp)
     |  (if exp1 exp2 exp3)
     |  (while exp1 exp2)
     |  (begin exp1 ... expn)
     |  (f exp1 ... expn)

Abstract syntax for Impcore

When working with programs and programming languages as a formal system, better to use abstract syntax: The essential structure of the program; that is, the tree structure that captures the key components of syntactic categories.

Def = VAL     (Name, Exp)
    | DEFINE  (Name, Namelist, Exp)
    | EXP     (Exp)

Exp = LITERAL (Int)
    | VAR     (Name)
    | SET     (Name, Exp)
    | IFX     (Exp, Exp, Exp)
    | WHILEX  (Exp, Exp)
    | BEGIN   (Explist)
    | APPLY   (Name, Explist)

Example AST for (f x (* y 3)) (using Explist)

Example AST for (define abs (x) (if (< x 0) (- 0 x) x)) (using Namelist)

Representing abstract syntax

typedef struct Exp *Exp;
typedef enum {
  LITERAL, VAR, SET, IFX, WHILEX, BEGIN, APPLY
} Expalt;        /* which alternative is it? */

struct Exp {  // only two fields: 'alt' and 'u'!
    Expalt alt;
    union {
        Value literal;
        Name var;
        struct { Name name; Exp exp; } set;
        struct { Exp cond; Exp true; Exp false; } ifx;
        struct { Exp cond; Exp exp; } whilex;
        Explist begin;
        struct { Name name; Explist actuals; } apply;
    } u;
};

Concrete vs. Abstract syntax

Parenthesized prefix (concrete) syntax is very close to abstract syntax.
(On purpose: converting this concrete syntax to abstract syntax is particularly easy.)

Alternative concrete syntax could correspond to same abstract syntax.

def ::= val x = exp
     |  define f (x1, ..., xn) { exp }
     |  exp

exp ::= integer
     |  x
     |  x := exp
     |  if (exp1) then exp2 else exp3
     |  while (exp1) do exp2 end
     |  { exp1 ; ... ; expn }
     |  f (exp1, ..., expn)
     | exp1 op exp2

op ::= + | - | * | / | = | < | >

Concrete syntax can sometimes be ambiguous:

  • Does 1 + 2 * 3 mean (1 + 2) * 3 or 1 + (2 * 3)?

Abstract syntax is always unambiguous.

Abstract syntax is like (good) HTML source: content without formatting

Concrete syntax is like browser (combining HTML source and CSS source): content w/ formatting

Compositionality

Both concrete and abstract syntax are examples of compositionality in action.

Programming-languages people are wild about compositionality:

  • Build sensible things from sensible pieces using just a few construction principles.

Why?

  • An infinite number of Impcore programs are described by these concise and precise syntax definitions.

  • Recursion and induction for processing and proving.

  • No awkward special cases; contrast with rules of English grammar.

Scoping rules for Impcore

What is the meaning of a name (variable or function) in an Impcore program?

Ways to talk about meanings of names:

  • Scope rules (older, mostly informal term)

  • Name spaces (mostly informal term, usually dealing with modules and separate compilation)

  • Symbol tables (compiler-specific, mostly informal term)

  • Environments (pointy-headed theory term)

We will use "environments" --- if you want to read some of the exciting papers, pointy-headed theory has to be second nature.

Impcore variables in 2 environments: globals, formals

There are no local variables:

  • Just like awk; if you need temporaries, use extra formal parameters:

    (define fact-w-ans (n ans)
      (if (< n 0)
        -1
        (begin
          (while (> n 0)
            (begin
              (set ans (* ans n))
              (set n (- n 1))))
          fact-ans)))
    
    (define fact (n) (fact-w-ans (n 1)))
  • For Programming #02, you will add local variables to the Impcore interpreter

Impcore functions in separate environment (not shared with variables).

More formal treatment of environments next week.

Environmental abuse

Abuse of separate environments

-> (val x 2)
2
-> (define x (y) (+ x y))
-> (define z (x) (x x))
-> (z 4)
6
  • Which xs correspond to globals?

  • Which xs correspond to formals?

  • Which xs correspond to functions?

Next week

Trick question: What’s the value of (* y 3)?

Better question: What’s the meaning of (* y 3)?

Recursion refresher

There are various ways in which a recursive function can decompose a natural number:

  • Decrement by one

    (define fact (n)
      (if (< n 0)
        -1
        (if (= n 0)
            1
            (* n (fact (- n 1))))))
    • Base case: n = 0

    • Recursive case: n = m + 1, recurse on m

  • Split into two pieces

    • Base case: n = 0

    • Recursive case: n = k + (n - k) (where 0 < k < n), recurse on n - k

  • Sequence of decimal digits

    • Base case: n = d (where 0 < d < 10)

    • Recursive case: n = 10 * m + d (where 0 < d < 10 and m > 0), recurse on m