Sie sind auf Seite 1von 47

Dening a Programming Language

How are programming languages actually dened? Say that you want to invent a nifty new language that you think will take the world by stormwhat do you do? There are two basic things that you need to specify: What do programs look like in your language (i.e., the syntax) Given a program in your language, what does it do when you execute it (i.e., the semantics) These are two very dierent things: the syntax only says what things look like ; it says nothing about what things mean. We tend to use mnemonic names in our syntax (like if...else, while, etc), but to the computer those names are meaningless. Its the semantics that actually species how to execute a program.
Ben Hardekopf () Syntax and Semantics Spring 2013 1 / 41

Syntax vs Semantics
Quick Quiz
A. 4

(1 + (2 3)) 3
B. 10 C. 13

What does the above expression evaluate to?

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

2 / 41

Syntax vs Semantics
Quick Quiz
A. 4

(1 + (2 3)) 3
B. 10 C. 13

What does the above expression evaluate to?

Of course this is a trick question. The answer is: it depends on the semantics! This is an important concept to internalize; otherwise you can easily get tripped up when moving to a new language that has similar syntax but dierent semantics. For example, when moving from Java to C++ the program excerpt: a = new int[5]; a[10] = 42; looks exactly the same in the two languages, but will have very dierent behaviors.
Ben Hardekopf () Syntax and Semantics Spring 2013 2 / 41

How To Specify Syntax and Semantics


OK, but how do we specify the syntax and semantics?

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

3 / 41

How To Specify Syntax and Semantics


OK, but how do we specify the syntax and semantics? Syntax Its generally agreed that the best method is Context-Free Grammars, the same mathematical formalism you studied in CS 138; everyone uses this method. CS 160 covers this topic in great detail.

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

3 / 41

How To Specify Syntax and Semantics


OK, but how do we specify the syntax and semantics? Syntax Its generally agreed that the best method is Context-Free Grammars, the same mathematical formalism you studied in CS 138; everyone uses this method. CS 160 covers this topic in great detail. Semantics Reference implementation

Too specic, and too complex because of optimizations Too imprecise and confusing, too large and unwieldy Requires mathematical sophistication Goal is clarity and precision, not performance
Syntax and Semantics Spring 2013 3 / 41

Prose description

Formal semantics

Denitional interpreters

Ben Hardekopf ()

Section Outline

Dening Programming Languages Dening syntax


Concrete and abstract syntax, ASTs The syntax of basic miniJS Implementing basic miniJS syntax in Scala

Dening semantics

Interpreter architectures A denitional interpreter for basic miniJS

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

4 / 41

Dening Syntax
Well only look at the very basic concepts of dening syntax (this topic is covered in much greater detail in CS 160). There are two kinds of syntax that a language developer needs to worry about: concrete and abstract.

Denition (Concrete Syntax)


This is the syntax that is visible to the programmer, that they type into the computer to write a program. To the computer, this is just a meaningless sequence of characters.

Denition (Abstract Syntax)


This is the internal syntax that the interpreter sees; its a simplied, structured representation of the program that the interpreter can traverse. Concrete Syntax parser Abstract Syntax Tree

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

5 / 41

Abstract Syntax Tree (AST)


Example (Concrete vs Abstract Syntax)
Concrete Expression (2 / 1 + 3) 4 2 1 Abstract Syntax Tree + 3 4

In this class well only worry about dening the AST data structure; we assume that the parsing is done for us.

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

6 / 41

Example: AST for Arithmetic Expressions


Example (Arithmetic Expressions)
a AExp ::= n Z | a1 + a2 | a1 a2 | a1 a2 | a1 a2 We use the usual convention that a is a metavariable standing for some member of AExp and that a1 and a2 are distinct members of AExp . Think of the as as non-terminals and the other symbols as terminals. This denition says that an arithmetic expression is either an integer n or two arithmetic expressions joined together by one of +, , , or . Notice that the denition is recursive, in the sense that a is used in its own denitionthis simple grammar denes an innite number of possible arithmetic expressions.

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

7 / 41

ASTs Resolve Ambiguity


The concrete syntax needs to prevent ambiguity syntactically (e.g., by adding parentheses and braces). The abstract syntax doesnt need to worry about ambiguity because its represented using an AST.

Example (Two possible ASTs for the expression 1 + 2 3 4)


+ 1 + 1 2 3 4 2 3 4

The structure of the AST resolves any possible ambiguity.

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

8 / 41

AExp AST in Scala


Scala AST for Arithmetic Expressions
sealed abstract class AExp case class Num(n:BigInt) extends case class Add(a1:AExp, a2:AExp) case class Sub(a1:AExp, a2:AExp) case class Mul(a1:AExp, a2:AExp) case class Div(a1:AExp, a2:AExp) AExp extends extends extends extends

AExp AExp AExp AExp

Example (AST on left, Scala on Right)


+ 1 2 3 4 Sub( Add(Num(1), Num(2)), Mul(Num(3), Num(4)) )
Syntax and Semantics Spring 2013 9 / 41

Ben Hardekopf ()

Dening Syntax for Basic miniJS


As an example, well dene the abstract syntax for a simple subset of miniJS then show (1) how the abstract syntax diers from the concrete syntax, and (2) how the abstract syntax is dened in Scala. Basic miniJS Language Features: Integers, booleans, and strings (with appropriate operators) Assignments Conditionals (if...else) While loops Sequencing Input/Output

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

10 / 41

Basic miniJS Abstract Syntax

nZ

b Bool

s String

x Variable

t Term ::= c | e c Cmd ::= t | x := e | while e t | output e e Exp ::= n | b | s | undef | x | e | e1 e2 | if e t1 else t2 | input typ | var xi in t typ InputType ::= num | str UnOp ::= | BinOp ::= + | | | | | | = | = | | <

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

11 / 41

Example Program

Concrete Syntax
var a, b, c in { a := 10; b := 0; while (a != 0) { b := b * (a + 1); a := a - 1 }; c := a + b }

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

12 / 41

Abstract Syntax Tree


var a b c seq := a 10 b := 0 a = 0 b b a
Ben Hardekopf () Syntax and Semantics

while seq := + 1 a a := 1 c

:= + a

Spring 2013

13 / 41

Basic miniJS AST in Scala


Every non-terminal with multiple options is turned into an abstract class; every phrase is turned into a class that inherits from the appropriate base class and has elds corresponding to that phrases non-terminals.
sealed abstract class Term // commands sealed abstract class Cmd extends Term case class Then(ts:Seq[Term]) extends Cmd case class Assign(x:Var, e:Exp) extends Cmd case class While(e:Exp, t:Term) extends Cmd case class Output(e:Exp) extends Cmd

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

14 / 41

ASTs in Scala contd


// expressions sealed abstract class Exp extends Term case class Num(n:BigInt) extends Exp case class Bool(b:Boolean) extends Exp case class Str(s:String) extends Exp case class Undef() extends Exp case class Var(x:String) extends Exp case class UnOp(op:Uop, e:Exp) extends Exp case class BinOp(op:Bop, e1:Exp, e2:Exp) extends Exp case class If(e:Exp, t1:Term, t2:Term) extends Exp case class In(typ:InputType) extends Exp case class Let(xs:Seq[Var], t:Term) extends Exp // types for input sealed abstract class InputType case object NumIT extends InputType case object StrIT extends InputType

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

15 / 41

ASTs in Scala contd contd


// unary operators sealed abstract class Uop case object extends Uop case object extends Uop // binary operators sealed abstract class Bop case object + extends Bop case object extends Bop case object extends Bop case object extends Bop case object extends Bop case object extends Bop case object = extends Bop case object = extends Bop case object extends Bop case object < extends Bop

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

16 / 41

Example AST in Scala

Let( Seq( Var(a), Var(b), Var(c) ), Then( Assign( Var(a), Num(10) ), Assign( Var(b), Num(0) ), While( UnOp( = , Var(a), Num(0) ), Then( Assign( Var(b), BinOp( , Var(b), BinOp( + , Var(a), Num(1) ))), Assign( Var(a), BinOp( , Var(a), Num(1) ) ) ) ) Assign( Var(c), BinOp( + , Var(a), Var(b) ) ) ) ) The important thing to take away is that the AST is just a tree data structure where the nodes are miniJS abstract syntax.
Ben Hardekopf () Syntax and Semantics Spring 2013 17 / 41

Section Outline

Dening Programming Languages Dening syntax


Concrete and abstract syntax, ASTs The syntax of basic miniJS Implementing basic miniJS syntax in Scala

Dening semantics

Interpreter architectures A denitional interpreter for basic miniJS

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

18 / 41

Dening Things
Denition (Denition)
A denition is an explanation of an unfamiliar concept in terms of more familiar concepts.

Denition (Denitional Interpreter)


A denitional interpreter explains an unfamiliar language in terms of familiar language features. So when writing a denitional interpreter, we need to:
1

Decide what language features we should assume the audience will be familiar with (this is a subjective decision). Implement our new language using only those features.
Ben Hardekopf () Syntax and Semantics Spring 2013 19 / 41

Architectures for Denitional Interpreters


There are a variety of ways that we could structure a denitional interpreter; which one is best depends on the specic context. Denotational interpreter Small-step operational interpreter Big-step operational interpreter Well look at the advantages and disadvantages of each in turn. In the following discussion well use these terms: Object language: the language whose semantics were dening Metalanguage: the language were using to dene the object language (i.e., the one we assume the audience is familiar with)

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

20 / 41

Denotational Intepreters
A denotational interpreter translates an object language AST into a program in the metalanguageits essentially a compiler.

Example (Object AST)


+ x 1

Example (Scala function)


( :Store) { (x) + 1 }

If the object and meta languages have similar language features then a denotational interpreter can be very clear and concise. However, the more dierent the languages are the messier and more complicated the translation will be. Any non-trivial object language almost requires the metalanguage to have higher-order functions. If the audience is familiar with them this is ne, otherwise a denotational interpreter isnt a good choice.
Ben Hardekopf () Syntax and Semantics Spring 2013 21 / 41

Small-step Operational Interpreter


A small-step operational interpreter makes very minimal assumptions about what language features are familiarit doesnt even use functions. Instead, it simulates its own runtime stack and species the evaluation as a series of very simple (small) steps.

Example (Object AST)


+ x 1

Example (Small steps)


Push +, 1 onto the stack Look up the value of x; call the result v Pop 1 and push v Check that 1 is a value Pop v and + o the stack Add 1 to v

A small-step interpreter spells out the evaluation of the AST in excruciating detail However, it can make specifying behaviors not present in the metalanguage very easy.
Ben Hardekopf () Syntax and Semantics

Spring 2013

22 / 41

Big-step Operational Interpreter


A big-step interpreter is sort of a middle-ground; it doesnt use higher-order functions, but it does use functions. It species the object languages evaluation as a recursive traversal of the AST.

Example (Object AST)


+ x 1

Example (Scala code)


eval(x) + eval(1)

Its big-step because eval() directly maps an AST to its nal value instead of breaking the evaluation down into lots of small steps. This strategy is more convenient than small-step unless the object languages behavior doesnt map well to a recursive AST traversal.

E.g., control operators that jump directly to widely-separated nodes such as goto and exceptions
Syntax and Semantics Spring 2013 23 / 41

Ben Hardekopf ()

Focusing on Big-step Interpreters


Well be using big-step interpreters in this class, where the metalanguage is Scala. There are three basic ingredients that we need to dene: Values: the set of object language values expressed as Scala data structures; we ll use an abstract class called Value. Conguration: for non-trivial object languages well usually need some extra information while evaluating the AST; this information will be held in various data structures. Evaluation function: This is the function that recursively traverses the AST mapping nodes to Values (modifying and referring to the conguration data structures as necessary).

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

24 / 41

Example: Big-step Interpreter for Arithmetic Expressions


Example (Scala Denition of AST for AExp)
sealed abstract class AExp case class Num(n:BigInt) extends case class Add(a1:AExp, a2:AExp) case class Sub(a1:AExp, a2:AExp) case class Mul(a1:AExp, a2:AExp) case class Div(a1:AExp, a2:AExp) AExp extends extends extends extends

AExp AExp AExp AExp

Example (Scala Denition of Values)


type Value = BigInt AExp is such a simple language that we dont need any extra data structures, so theres no conguration.
Ben Hardekopf () Syntax and Semantics Spring 2013 25 / 41

Example: AExp Interpreter contd


Example (Scala Evaluation Function)
def eval(a:AExp): Value = a match { case Num(n) n case Add(a1, a2) eval(a1) + eval(a2) case Sub(a1, a2) eval(a1) - eval(a2) case Mul(a1, a2) eval(a1) * eval(a2) case Div(a1, a2) ( eval(a1), eval(a2) ) match { case (v1, v2) if v2 != 0 v1 / v2 case _ throw new Exception("undefined behavior") } } Some interesting points: Using BigInt instead of Int Undened behavior (and our specic choice)
Ben Hardekopf () Syntax and Semantics Spring 2013 26 / 41

Example: Evaluating an AExp Expression


Apply the AExp semantics to the following expression:

AST on left, Scala Data Structure on Right


+ 1 2 3 4 Sub( Add(Num(1), Num(2)), Mul(Num(3), Num(4)) )

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

27 / 41

Example: Evaluating an AExp Expression


Apply the AExp semantics to the following expression:

AST on left, Scala Data Structure on Right


+ 1 2 3 4 Sub( Add(Num(1), Num(2)), Mul(Num(3), Num(4)) )

Solution
-9

12

+
2

4
Spring 2013 27 / 41

Ben Hardekopf ()

Syntax and Semantics

Interpreter for Basic miniJS


Example (Concrete Program)
var s, times in output "What string should I double?"; s := input str; output "How many times should I double it?"; times := input num; while (times != 0) { s := s + s; times := times - 1 }; output s What should the language values be? What should the conguration be?

Hint: x := y + 1
Syntax and Semantics Spring 2013 28 / 41

Ben Hardekopf ()

Basic miniJS Values


miniJS Values
sealed abstract class Value sealed abstract class Storable extends Value case case case case class class class class NumV(n:BigInt) extends Storable BoolV(b:Boolean) extends Storable StrV(s:String) extends Storable UndefV() extends Storable

A storable Value is simply a value that can be put into the store. For now all values are storable, but when we get into exceptions and other control operators well also have non-storable Values.

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

29 / 41

An Example Program
var a, b in b := 1; a := var b in { b := 40; b + 1 }; output a + b

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

30 / 41

An Example Program
var a, b in b := 1; a := var b in { b := 40; b + 1 }; output a + b

prompt% scala example.not 81 prompt% What happened?!


Ben Hardekopf () Syntax and Semantics Spring 2013 30 / 41

Scope Issues
Conceptually we need to map variables to values in order to evaluate a program, but as weve seen a naive solution leads to dynamic scope. We need some way to make sure were looking up the right variable.

Denition (Scope)
Scope is about visibility at a particular point in the program, what variables are visible? In particular, if there are several variables with the same name, which one is being used?

Denition (Dynamic Scope)


The variable being used is the most recently bound version.

Denition (Lexical Scope, aka Static Scope)


The variable being used is the syntactically closest binding.

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

31 / 41

Example Programs AST


var a b seq := b 1 a := var b seq := b 40 b + 1 a output + b

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

32 / 41

How Does C Do It?


int foo( int x ) { x--; if (x == 0) return x else return foo(x) + x } void main() { printf( "%d\n", foo(3) ) }

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

33 / 41

How Does C Do It?


int foo( int x ) { x--; if (x == 0) return x else return foo(x) + x } void main() { printf( "%d\n", foo(3) ) } The C runtime uses a stack; when a function is invoked a new stack frame is pushed on the stack and that functions local variables (including parameters) are mapped to the memory in that stack frame.

When looking up a variables value, we look at the top-most instance of the variable on the stack. When a function is done its stack frame is popped o the stack and that particular invocations variable values are forgotten.
Syntax and Semantics Spring 2013 33 / 41

Ben Hardekopf ()

Basic miniJS Conguration


Our solution is to use an environment. Instead of mapping variables directly to values, we insert an extra level of indirection: the environment maps variables to addresses, and the store maps addresses to values.

Conguration
a Address = Z Env = Variable Address Store = Address Storable The environment will act kind of like a stack: when we enter a new scope well add new bindings that override any old bindings, and when we leave a scope well remove those bindings (revealing any old bindings that had been overridden).
Ben Hardekopf () Syntax and Semantics Spring 2013 34 / 41

Revisiting the Example

var a, b in b := 1; a := var b in { b := 40; b + 1 }; output a + b

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

35 / 41

Basic miniJS Interpreter

See miniJS (basic) interpreter

Available on the class website

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

36 / 41

Example Evaluation

Show the execution of the following program:

Concrete Program
var a, b, c in a := 1; b := 1; while (a != 0) { b := b * (a + 1); a := a - 1 }; c := a + b

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

37 / 41

AST
var a b c seq := a 1 b := 1 a = 0 b := b a + 1 a while seq a := 1 a c := + b

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

38 / 41

Exercise

Show the execution of the following program:

Concrete Program
var x, y in x := 0; y := 0; if (y <= x) { while (y <= x) { y := x + 1 } } else { y := x - 1 }; output y

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

39 / 41

AST

var x y seq := x 0 y := 0 y x y x x if while y := + 1 x y := 1 output y

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

40 / 41

Basic miniJS is Turing-Complete


Despite its simplicity, basic miniJS is in fact a fully Turing-complete language capable of computing anything that more complex languages such as C or Java can compute. However, this fact may not be obvious.

Puzzle (Extra Credit)


Part 1 Implement a sorting algorithm in basic miniJS that reads in an arbitrary number of integers from the user and then prints them out in sorted order. Part 2 Implement a Universal Turing Machine (or some other Turing-complete language) in basic miniJS; this is a proof that the language is Turing-complete.

Ben Hardekopf ()

Syntax and Semantics

Spring 2013

41 / 41

Das könnte Ihnen auch gefallen