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
or1 + (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
x
s correspond to globals? -
Which
x
s correspond to formals? -
Which
x
s 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
-