Recursive Descent (contd)

Slides:



Advertisements
Similar presentations
CPSC 388 – Compiler Design and Construction
Advertisements

Semantic Analysis Chapter 6. Two Flavors  Static (done during compile time) –C –Ada  Dynamic (done during run time) –LISP –Smalltalk  Optimization.
CSE 332: C++ exceptions Overview of C++ Exceptions Normal program control flow is halted –At the point where an exception is thrown The program call stack.
Environments and Evaluation
1 Run time vs. Compile time The compiler must generate code to handle issues that arise at run time Representation of various data types Procedure linkage.
Review of C++ Programming Part II Sheng-Fang Huang.
 2006 Pearson Education, Inc. All rights reserved Classes: A Deeper Look.
C++ Programming: Program Design Including Data Structures, Fourth Edition Chapter 13: Pointers, Classes, Virtual Functions, and Abstract Classes.
Java and C++, The Difference An introduction Unit - 00.
C++ Programming: From Problem Analysis to Program Design, Fourth Edition Chapter 14: Pointers, Classes, Virtual Functions, and Abstract Classes.
© The McGraw-Hill Companies, 2006 Chapter 4 Implementing methods.
1 Chapter 10: Data Abstraction and Object Orientation Aaron Bloomfield CS 415 Fall 2005.
Object Oriented Programming with C++/ Session 6 / 1 of 44 Multiple Inheritance and Polymorphism Session 6.
CSSE501 Object-Oriented Development. Chapter 12: Implications of Substitution  In this chapter we will investigate some of the implications of the principle.
OOP and Dynamic Method Binding Chapter 9. Object Oriented Programming Skipping most of this chapter Focus on 9.4, Dynamic method binding – Polymorphism.
Interpretation Environments and Evaluation. CS 354 Spring Translation Stages Lexical analysis (scanning) Parsing –Recognizing –Building parse tree.
Formal Semantics Chapter Twenty-ThreeModern Programming Languages, 2nd ed.1.
Programming Languages by Ravi Sethi Chapter 6: Groupings of Data and Operations.
CPSC 252 The Big Three Page 1 The “Big Three” Every class that has data members pointing to dynamically allocated memory must implement these three methods:
CMSC 202 Advanced Section Classes and Objects: Object Creation and Constructors.
Quick Review of OOP Constructs Classes:  Data types for structured data and behavior  fields and methods Objects:  Variables whose data type is a class.
1 C# - Inheritance and Polymorphism. 2 1.Inheritance 2.Implementing Inheritance in C# 3.Constructor calls in Inheritance 4.Protected Access Modifier 5.The.
CSE 3341/655; Part 3 35 This is all very wrong! What would the SW1/SW2 (RESOLVE) people say if they saw this? Problem: Lack of data abstraction; What would.
CSE 332: C++ Exceptions Motivation for C++ Exceptions Void Number:: operator/= (const double denom) { if (denom == 0.0) { // what to do here? } m_value.
LECTURE 10 Semantic Analysis. REVIEW So far, we’ve covered the following: Compilation methods: compilation vs. interpretation. The overall compilation.
Design issues for Object-Oriented Languages
CS314 – Section 5 Recitation 9
Implementing Subprograms
Memory Management.
Inheritance Modern object-oriented (OO) programming languages provide 3 capabilities: encapsulation inheritance polymorphism which can improve the design,
Functions Students should understand the concept and basic mechanics of the function call/return pattern from CS 1114/2114, but some will not. A function.
Topic: Classes and Objects
User-Written Functions
Overview 4 major memory segments Key differences from Java stack
Scope and Code Generation
Chapter 13: Pointers, Classes, Virtual Functions, and Abstract Classes
7. Inheritance and Polymorphism
Andy Wang Object Oriented Programming in C++ COP 3330
Semantic Analysis with Emphasis on Name Analysis
Core Core: Simple prog. language for which you will write an interpreter as your project. First define the Core grammar Next look at the details of how.
Interfaces and Inheritance
This pointer, Dynamic memory allocation, Constructors and Destructor
CSC 253 Lecture 8.
Chapter 12: Pointers, Classes, Virtual Functions, and Abstract Classes
Implementing Subprograms
CSC 253 Lecture 8.
Overview 4 major memory segments Key differences from Java stack
Chapter 15 Pointers, Dynamic Data, and Reference Types
Overview of Memory Layout in C++
Lecture 15 (Notes by P. N. Hilfinger and R. Bodik)
CSC 533: Programming Languages Spring 2015
PZ09A - Activation records
Activation records Programming Language Design and Implementation (4th Edition) by T. Pratt and M. Zelkowitz Prentice Hall, 2001 Section
UNIT V Run Time Environments.
9-10 Classes: A Deeper Look.
Overview of C++ Polymorphism
Course Overview PART I: overview material PART II: inside a compiler
Exceptions 10-May-19.
Pointers and References
CSC 533: Programming Languages Spring 2018
The Three Attributes of an Identifier
CMSC 202 Exceptions.
CSC 533: Programming Languages Spring 2019
More C++ Classes Systems Programming.
9-10 Classes: A Deeper Look.
Classes and Objects Object Creation
Activation records Programming Language Design and Implementation (4th Edition) by T. Pratt and M. Zelkowitz Prentice Hall, 2001 Section
Implementing Subprograms
SPL – PS2 C++ Memory Handling.
CMSC 202 Constructors Version 9/10.
Presentation transcript:

Recursive Descent (contd) This is all very, *very* wrong! What would the SW1/SW2 people say if they saw this? Problem: Lack of data abstraction: "client code" is intimately tied to "provider" details What would happen if we changed the PT rep? We need a ParseTree class! Key point: Parser, Printer, Executor functions should not know the internals of the PT class CSE 3341/655; Part 3

ParseTree class What are the ParseTree operations? First: Need -- not just a ParseTree object, but a cursor to track where we are in PT Some ParseTree operations: int currNTNo(); // non-term. at current node int currAlt(); // Alternative no. used at current node void goDownLB(); // move cursor to left child void goDownMB(); //move cursor to second child void goDownRB(); //move cursor to third child How do we go up? void goUp() // move cursor to parent node CSE 3341/655; Part 3

Recursive Descent void PrintIf(ParseTree& p) { write( “if” ); p.goDownLB(); PrintCond(p); p.goUp(); p.goDownMB(); PrintSS(p); // bug! p.goUp(); x = p.currAlt(); if (x == 2) then { p.goDownRB(); write (“else”); PrintSS(p); }; write(“end ;”); } // Bug! Compare: void printIf(int n) { write("if); printCond(PT[n,3]); write("then"); printSS(PT[n,4]); if (PT[n,2]==2) … }

ParseTree class We didn’t see how the ParseTree is represented or what a node looks like - e.g.: how do we “go back up”? That is the point of data abstraction! You could use an array rep or pointers or whatever you like; the client (Parser/Printer/Executor) will be unaffected! CSE 3341/655; Part 3

ParseTree class (contd.) Need some more operations: If non-term at current node is <id>, need its name, value etc. int currIdVal(); // error if we are not at <id> node void setIdVal(int x); string idName(); void ExecAssign(ParseTree p) { p.goDownMB(); x = EvalExp(p); p.goUp(); p.goDownLB(); p.setIdVal(x); } // Bug! CSE 3341/655; Part 3

ParseTree class Problem: What about the parser? Key point: The parser adds nodes Hence: Need more operations Assumption: Calling procedure will create an “empty” node, set the cursor to that node, then call the appropriate parse procedure. CSE 3341/655; Part 3

Parsing void ParseAssign(ParseTree& p) { p.setNTNo(7); p.setAltNo(1); // why? p.createLB(); p.createMB(); p.goDownLB(); ParseId(p); p.skipToken(); // why? and why p.skipToken? p.goUp(); p.goDownMB(); ParseExp(p); p.skipToken(); p.goUp; } CSE 3341/655; Part 3

Object-oriented? That is better than the original version but ... ... is not object-oriented. Object-oriented: Treat each node as a separate, independent object. Have a separate class for each non-terminal Each <if> stmt. is an instance of the If class; each <exp> an instance of the Exp class; ... CSE 3341/655; Part 3

Object-oriented approach (contd) class Prog { private: DS* ds; SS* ss; public: Prog() { ds = nil; ss = nil; } PrintProg() { write(“program”); ds→PrintDS(); write(“begin”); ss→PrintSS(); print(“end”); } ExecProg() { ds→ExecDS(); ss→ExecSS(); } // not quite CSE 3341/655; Part 3

Object-oriented approach (contd) class Prog{ private: DS* ds; SS* ss; public: Prog() { ds = nil; ss = nil; } ParseProg() { skipToken(); // error check? ds = new DS(); ds→ParseDS(); ss = new SS(); ss→ParseSS(); // bug! skipToken(); skipToken();} CSE 3341/655; Part 3

Object-oriented approach (contd) class If{ private: Cond* c; SS* ss1; SS* ss2; // why? public: If() { c = nil; ss1 = nil; ss1 = nil; } ParseIf() { int tokNo; skipToken(); // why? c = new Cond(); c→ParseCond(); skipToken(); ss1 = new SS(); ss1→ParseSS(); tokNo=getToken(); if (tokNo==“end”) {skipToken(); skipToken(); return; } skipToken(); // error check? ss2 = new SS(); ss2→ParseSS(); ..skip tokens.. } CSE 3341/655; Part 3

Object-oriented approach (contd) class If{ // contd. Print If() { write(“if ”); c→PrintCond(); write(“then”); ss1→PrintSS(); ... how to check alt. no? ... ... } // Each class can use its own approach ExecIf() { if (c→EvalCond()) then {ss1→ExecSS(); return;} if (!ss2) {ss2→ExecSS();} } CSE 3341/655; Part 3

OO Languages During execution of an OO program, it is best to think of memory being organized into three pieces: Code portion: contains the (compiled) code of running progam Stack portion: organized as a collection of "activation records" (or "frames"); a new a.r. is created when a function/method is called … and released when the function/method returns What does an a.r. contain? … Heap: contains objects that are created by calls to "new"; each call to "new" assigns memory as specified in the variables of the class in question … when is this memory released? Back to the Core interpreter … CSE 3341/655; Part 3

Object-oriented approach (contd) class Stmt{ // consider what happens on stack and on heap private: int altNo; Assign* s1; IF* s2; Loop* s3; Input* s4; Output* s5; public: Stmt() { altNo=0; s1 = nil; ... } parseStmt() { int tokNo; tokNo = tok.getToken(); if (tokNo==...) { altNo=1; s1= new Assign(); s1->parseAssign();} if (tokNo==...) { altNo=2; s2= new IF(); s2->parseIF();} if (tokNo==...) { altNo=3; s3= new Loop(); s3->parseLoop();} if (tokNo==...) { altNo=4; s4= new Input(); s4->parseInput();} if (tokNo==...){altNo=5;s5= new Output(); s5->parseOutput();}} printStmt() { if (altNo==1) {s1->printAssign(); return; } ... } execStmt() { if (altNo==1) {s1->execAssign(); return; } ... } CSE 3341/655; Part 3

Problem with Id class Id class poses some problems: Need to make sure that two occurrences of same Id are not treated as two objects: class Assign { Id* id; Exp* exp; ParseAssign() { id = new Id(); id→ParseId(); // Won’t Work! Need to check if the id in question already exists; and create a new one only if it does not. Solution: Make ParseId() a static method which will decide whether to create a new Id object CSE 3341/655; Part 3

Problem with Id class (contd) class Assign { Id* id; Exp* exp; // nothing new here ParseAssign() { id = Id::ParseId(); // ParseAssign does not create a new // Id object since this Id object may already exist exp = new Exp(); expParseExp(); ... // nothing new} ... Static methods such as ParseId -- also called "class methods" Can be used even if we don't have an instance of the class But: in their bodies *cannot* refer to the normal member variables of the class! (why not?); they can only refer to static variables "static" ≡ single copy that exists for the duration of program

Problem with Id class (contd) class Id { //what happens on stack, heap? string name; int val; bool declared; bool initialized; static Id* eIds[20]; // pointers to existing Id’s; *static* var static int idCount = 0; // also static Id(string n) { // *private* constructor! name = n; declared = false; initialized = false; } public: static Id* ParseId() { // get token; check if name matches eIds[k]→name for // any k = 0, ..., idCount-1; if yes, return eIds[k]; // if not: Id* nId = new Id(n1); ...add to eIds[]... return nId; } int getIdVal() {return val; // check initialized first? setIdVal()? getIdName()? ... CSE 3341/655; Part 3

C++ : complications with static variables C++ needs special syntax for dealing with static variables: class Id { static Id* eIds[20]; static int idCount = 0; That is wrong -- you can't initialize in the header More generally, these are *declarations*; you also need defs; so change above to: static Id* eIds[]; static int idCount; Then outside the class: Id* Id::eIds[20]; // this is the def & allocates space int Id::idCount = 0; // this is the def & allocates space CSE 3341/655; Part 3

Similar problem with Tokenizer We have a number of parse methods: ParseProg(), ParseDS(), ParseDecl(), ParseSS() etc. Each will need to use the tokenizer How to ensure there is only one? In the ParseTree class approach: Include a tokenizer object as part of the parseTree object; and have ParseTree class provide methods, getToken(), skipToken(), etc. These methods will simply call the corresponding methods of Tokenizer class on the tokenizer object Since there is only one ParseTree object, this will work CSE 3341/655; Part 3

In the OO approach … Global variable containing the single tokenizer Pass the single tokenizer around as a parameter Singleton class: Make constructor private (or protected); Have a local static variable (initialized to null) to hold a reference to the single instance; Provide a method Instance() that returns that

Singleton class class C { static C* instance = null; C(){ } // private constructor public: static C* Instance(){ //like ParseId() if (instance == 0) {instance = new C();} return instance; } ... }; //problematic for use for Tokenizer Scope vs life-time: Scope of a variable/object: In which parts of the program do we have access to it? Life-time: When does the object come into existence and when does it go out of existence? Scope, life-time of static variables? Of non-static member variables?

Types and Related Issues (Book chapters: 7, 9, 15(?), …) Key idea: Put as much info as possible into the type of each variable … then system can catch a lot of the errors Strong Typing: All type errors are caught by system Strong vs. weak types must match, only explicit conv., vs. implicit type conversion (Ruby vs. JS) Static vs. dynamic typing (pre-run-time vs. run-time) Duck typing: dynamic, “deduced type” … later Which is best? Why? (Course homepage has link to a brief paper on this) CSE 3341; Part 3

OO-approach to interpreter is more strongly typed than the ParseTree class approach (why?) … but type errors that the compiler misses are still possible <exp> ::= <int> | <exp> + <exp> | <exp> * <exp> class Exp { private: int kind; int i; Exp* e1; Exp* e2; public: int evalExp() { if (kind==1) return(i); if (kind==2) return( e1→evalExp() + e2→evalExp() ); if (kind==3) return( e1→evalExp() * e2→evalExp() ); } But ... what if the value of kind is wrong? Isn't this a type problem that the compiler should handle? The problem cannot be solved by simply having three separate types (IntExp, SumExp, ProdExp); why not?

Types and Related Issues Solution: OO polymorphism: Three classes for the three different types of Exp but also include the notion of a generic Exp class Exp { public: virtual int evalExp() = 0; // "abstract" in Java: bool isEven() { // not virtual int k = evalExp(); // on which object? return ((k%2) == 0) ; } }; Note: Every (non-static!) method of a class has an implicit parameter (named this or self) … passed by ref? copy? CSE 3341; Part 3

class Exps : public Exp { // sum expression private: Exp* e1; Exp* e2; // note the types of e1, e2 public: Exps( Exp* e, Exp* f) { e1 = e; e2 = f; } int evalExp() { return(e1→evalExp() + e2→evalExp()); } }; class Expp : public Exp { // product expression private: Exp* e1; Exp* e2; Expp( Exp* e, Exp* f) { e1 = e; e2 = f; } int evalExp() { return(e1→evalExp() * e2→evalExp()); } }; class Expi : public Exp { // integer expression private: int i; public: Expi( int j) { i = j; } int evalExp() { return i; } }; CSE 3341; Part 3

Polymorphism: Implementation Each object carries with it a table of addresses(?) of all virtual methods applicable to it. Exp* ep; ...; ep→evalExp(); ... At compile time, we don't know exact type of <exp> that ep will point to. So produce code that gets the address of evalExp() from the object, then "dispatches" to that addr. This is run-time dispatching. Instead of programmer using "if" statements, the system dispatches. The exact function to be called is not known until run-time ... but it is type-safe! Consider how isEven() works CSE 3341; Part 3

Polymorphism So: for each (non-abstract) class, the compiler creates a table with addresses, in a specific order, of implementations of virtual methods that apply to instances of that class; each object, when constructed, includes a pointer to that. Is it possible to carry OO polymorphism too far? We will address that question as a group activity … CSE 3341; Part 3

Kinds of typing Strong: types must match, only explicit conversion Weak: implicit type conversion Static typing: types are checked at compile time Dynamic: types are checked at runtime Ideal: Strongly- and statically- typed In practice: not all type checking is at compile time Duck typing is a style of dynamic typing. With duck typing, the set of methods and properties determine the valid semantics of a class, instead of inheritance from a specific class (or impl. of an interface) "Duck Typing" (as in Ruby?): The standard implementation approach does not work for these languages; not only must each object carry addresses of "its" methods, must also carry their names etc. … why? So what? CSE 3341; Part 3