You are on page 1of 16

Page 1 of 16

What is BNF? Backus-Naur notation (more commonly known as BNF or Backus-Naur Form) is a formal mathematical way to describe a language, which was developed by John Backus (and possibly Peter Naur as well) todescribe the syntax of the Algol 60 programming language. (Legend has it that it was primarily developed by John Backus (based on earlier work by the mathemathician Emil Post), but adopted and slightly improved by Peter Naur for Algol 60, which made it well-known. Because of this Naur calls BNF Backus Normal Form, while everyone else calls it Backus- Naur Form.) It is used to formally define the grammar of a language, so that there is no disagreement or ambiguity as to what is allowed and what is not. In fact, BNF is so unambiguous that there is a lot of mathemathical theory around these kinds of grammars, and one can actually mechanically construct a parser for a language given a BNF grammar for it. (There are some kinds of grammars for which this isn't possible, but they can usually be transformed manually into ones that can be used.) Programs that do this are commonly called "compiler compilers". The most famous of these is YACC, but there are many more. How it works The principles BNF is sort of like a mathematical game: you start with a symbol (called the start symbol and by convention usually named S in examples) and are then given rules for what you can replace this symbol with. The language defined by the BNF grammar is just the set of all strings you can produce by following these rules. The rules are called production rules, and look like this: symbol := alternative1 | alternative2 ... A production rule simply states that the symbol on the left-hand side of the := must be replaced by one of the alternatives on the right hand side. The alternatives are separated by |s. (One variation on this is to use ::= instead of :=, but the meaning is the same.) Alternatives usually consist of both symbols and something called terminals. Terminals are simply pieces of the final string that are not symbols. They are called terminals because there are no production rules for them: they terminate the production process. (Symbols are often called non-terminals.) Another variation on BNF grammars is to enclose terminals in quotes to distinguish them from symbols. Some BNF grammars explicitly show where whitespace is allowed by having a symbol for it, while other grammars leave this for the reader to infer. There is one special symbol in BNF: @, which simply means that the symbol can be removed. If you replace a symbol by @ you do it by just removing the symbol. This is useful because in some cases it is difficult to end the replacement process without using this trick. So, the language described by a grammar is the set of all strings you can produce with the production rules. If a string cannot in any way be produced by using the rules the string is not allowed in the language. A real example Below is a sample BNF grammar: S := '-' FN | FN FN := DL |

Page 2 of 16

DL '.' DL DL := D | D DL D := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' The different symbols here are all abbreviations: S is the start symbol, FN produces a fractional number, DL is a digit list, while D is a digit. Valid sentences in the language described by this grammar are all numbers, possibly fractional, and possibly negative. To produce a number, start with the start symbol S: S Then replace the S symbol with one of its productions. In this case we choose not to put a '-' in front of the number, so we use the plain FN production and replace S by FN: FN The next step is then to replace the FN symbol with one of its productions. We want a fractional number, so we choose the production that creates two decimal lists with a '.' between them, and after that we keep choosing replacing a symbol with one of its productions once per line in the example below: DL . DL D . DL 3 . DL 3 . D DL 3.DD 3.1D 3.14 Here we've produced the fractional number 3.14. How to produce the number -5 is left as an exercise for the reader. To make sure you understand this you should also study the grammar until you understand why the string 3..14 cannot be produced with these production rules. Uses of BNF and EBNF Common uses Most programming language standards use some variant of EBNF to define the grammar of the language. This has two advantages: there can be no disagreement on what the syntax of the language is, and it makes it much easier to make compilers, because the parser for the compiler can be generated automatically with a compiler-compiler like YACC. EBNF is also used in many other standards, such as definitions of protocol formats, data formats and markup languages such as XML and SGML. (HTML is not defined with a grammar, instead it is defined with an SGML DTD, which is sort of a higher-level grammar.) o BNF stands for either Backus-Naur Form or Backus Normal Form o BNF is a meta language used to describe the grammar of a programming language o BNF is formal and precise o BNF is a notation for context-free grammars o BNF is essential in compiler construction o There are many dialects of BNF in use, but the differences are almost always minor o < > indicate a nonterminal that needs to be further expanded, e.g. <variable> o Symbols not enclosed in < > are terminals; they represent themselves, e.g. if, while, (

Page 3 of 16

The symbol ::= means is defined as The symbol | means or; it separates alternatives, e.g. <addop> ::= + | This is all there is to plain BNF; but we will discuss extended BNF (EBNF) later in this lecture <integer> ::= <digit> | <integer> <digit> or <integer> ::= <digit> | <digit> <integer> o Recursion is all that is needed (at least, in a formal sense) o "Extended BNF" allows repetition as well as recursion o Repetition is usually better when using BNF to construct a compiler o o o o Example1 <digit> ::= 0|1|2|3|4|5|6|7|8|9 <if statement> ::= if ( <condition> ) <statement> | if ( <condition> ) <statement> else <statement>

<unsigned integer> ::= <digit> | <unsigned integer> <digit> <integer> ::= <unsigned integer> | + <unsigned integer> | - <unsigned integer> EBNF: What is it, and why do we need it? In DL I had to use recursion (ie: DL can produce new DLs) to express the fact that there can be any number of Ds. This is a bit awkward and makes the BNF harder to read. Extended BNF (EBNF, of course) solves this problem by adding three operators: ? : which means that the symbol (or group of symbols in parenthesis) to the left of the operator is optional (it can appear zero or one times) * : which means that something can be repeated any number of times (and possibly be skipped altogether) + : which means that something can appear one or more times An EBNF sample grammar So in extended BNF the above grammar can be written as: S := '-'? D+ ('.' D+)? D := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' which is rather nicer. :) Just for the record: EBNF is not more powerful than BNF in terms of what languages it can define, just more convenient. Any EBNF production can be translated into an equivalent set of BNF productions.

Page 4 of 16

PROGRAMMING LANGUAGE DESIGN A programming language is an artificial language designed to communicate instructions to a machine, particularly a computer. Programming languages can be used to create programs that control the behavior of a machine and/or to express algorithms precisely. The description of a programming language is usually split into the two components of syntax (form) and semantics (meaning). Some languages are defined by a specification document (for example, the C programming language is specified by an ISO Standard), while other languages, such as Perl 5 and earlier, have a dominant implementation that is used as a reference. A programming language is a notation for writing programs, which are specifications of a computation or algorithm.[1] Some, but not all, authors restrict the term "programming language" to those languages that can express all possible algorithms.[1][2] Traits often considered important for what constitutes a programming language include:

On programming language design In a recent post I claimed that Pythons lambda construct is broken. This attracted some angry responses by people who thought I was confused about how Python works. Luckily there were also many useful responses from which I learnt. This post is a response to comment 27, which asks me to say more about my calling certain design decisions in Python crazy. Language design is like architecture. The architect is bound by the rules of nature, he has to take into account the properties of the building materials, and he must never forget the purpose that the building will serve. Likewise, the designer of a programming language is bound by the theorems of computability theory, he must take into account the properties of the underlying hardware, and he must never forget that the language is used by programmers. When I teach the theory of programming languages, I tell my students that there is a design principle from which almost everything else follows: Programmers are just humans: forgetful, lazy, and they make every mistake imaginable. Therefore, it is the task of the designer to make a programming language which counters these deficiencies. A language must not be too complex, lest the programmer forget half of it. A language must support the programmers laziness by providing lots of useful libraries, and by making it possible to express ideas directly and succinctly. The language must allow good organization of source code, otherwise the programmer will use the copy-paste method. The language must try really hard to catch programming mistakes, especially the mundane ones that happen to everyone all the time. When it finds a mistake, it must point to the true reason for it, preferably with an error message that humans understand. You will notice that so far I have not said a word about efficiency. If this were the year 1972 we would talk about efficiency first and forget about the programmers, because 37 years ago hardware and processing time were the scarcest resources. Today we live in different times when the most expensive resource is development time. In 1972 it was a good design decision to implement arrays in C so that they did not carry with them information about their lengths (save a couple of bytes on each array), it was a good decision not to check for out-of-bounds errors in array indexing (save a

Page 5 of 16

couple of CPU cycles), and it was a good decision not to have garbage collection (it didnt work well anyhow). From todays point of view all these decisions were horrible mistakes. Buffer overflows, which are a consequence of missing out-of-bounds checks, cost the industry huge amounts of money every year, while lack of automated garbage collection results in memory leaks that cause programs to be unstable. Of course, even today C might be just the right tool for your specific task. I am not saying that memory efficiency and speed are not important. They are not as important as they used to be. The first objective in a programming language design today should be friendliness to programmers. A lot is known about how to write an optimizing compiler and how to generate efficient code, so usually the design of the language does not prevent generation of efficient compiled or interpreted code. People do not make bad design decisions because they are evil or stupid. They make them because they judge that the advantages of the decision outweigh the disadvantages. What they often do not see is that they could have achieved the same advantages in a different way, without introducing the disadvantages. Therefore, it is very important to get the order right: first make sure the design avoids the disadvantages, then think about how to get the advantages back. Let us now apply these principles to several examples. Undefined values (NULL, null, undef, None) Suppose we want a language with references (pointers in C). The principle tells us that it is a bad idea to allow invalid references because programmers will create them. Indeed, most recently designed languages, say Java and Python, do not allow you to write obviously risky things, such as int *p = (int *)0xabcdef; Unfortunately, many designers have still not learnt that the special NULL pointer or null object is an equally bad idea. Pythons None, perls undef, and SQLs NULL all fall in the same category. I can hear you list lots of advantages of having these. But stick to the principle: NULL is wrong because it causes horrible and tricky mistakes which appear even after the program was tested thoroughly. You cannot introduce NULL into the language and tell the programmer to be careful about it. The programmer is not capable of being careful! There is plenty of evidence to support this sad fact. Therefore NULL, null, None and undef must go. I shall collectively denote these with Pythons None. Of course, if we take away None, we must put something else back in. To see what is needed, consider the fact that None is intended as a special constant that signifies missing value. Problems occur when a given value could be either proper or missing and the programmer forgets to consider the case of missing value. The solution is to design the language in such a way that the programmer is always forced to consider both possibilities. For example, Haskell does this with the datatype Maybe, which has two kinds of values:

Nothing, meaning missing value Just x, meaning the value is x

The only way to use such a value in Haskell is to consider both cases, otherwise the compiler complains. The language is forcing the programmer to do the right thing. Is this annoying? You will probably feel annoyed if you are used to ugly hacks with None, but a bit of experience will quickly convince you that the advantages easily outweigh your tendency for laziness. By the way, Haskell actually supports your laziness. Once you tell it that the type of a value is Maybe, it will find for you all

Page 6 of 16

the places where you need to be careful about Nothing. C, Java, Python, and perl stay silent and let you suffer through your own mistaken uses of NULLs, nulls, Nones, and undefs. Other languages that let you have the data type like Haskells Maybe are ML and Ocaml because they have sum types. Pascal, Modula-2 and C have broken sum types because they require the programmer to handle the tag by hand. Everything is an object (or list, or array) Many languages are advertised as simple because in them everything is expressed with just a couple of basic concepts. Lisp and scheme programmers proudly represent all sorts of data with conses and lists. Fortran programmers implement linked lists and trees with arrays. In Java and Python everything is an object, more or less. It is good to have a simple language, but it is not good to sacrifice its expressiveness to the point where most of the time the programmer has to encode the concepts that he really needs indirectly with those available in the language. Programmers cannot do such things reliably, and the compiler cannot help them with the task because it does not know what is in programmers head. Let us look at a typical example in scheme. Suppose we would like to represent binary trees in which the nodes are labeled with integers. In scheme we might do this by representing the empty tree as (), and use a three-element list (k l r) to represent a tree whose root is labeled by k, the left subtree is l, and the right subtree is r. A quick search on Google shows that this is a popular way of implementing trees in scheme. Its simple, its cool, its easy to explain to the students, but scheme will have no idea whatsoever what youre doing. There are a number of trivial mistakes which can be made with such a representation, and scheme wont detect them (at best you will get a runtime error): you might write (l k r) instead of (k l r), you might mistakenly pass a four-element list to a function expecting a tree, you might mistakenly think that the integer 42 is a valid representation of the tree (42 () ()), you might mistakenly try to compute the left subtree of the empty tree, etc. And remember, the programmer will make all these mistakes. With objects the situation is somewhat better. In Java we would define a class Tree with three attributes root, left, and right. It will be impossible to build a tree with a missing attribute, or too many attributes. But we will hit another problem: how to represent the empty tree? There are several choices none of which is ideal: 1. the empty tree is null: this is the worst solution, as any Java programmer knows 2. we define a class Tree and subclasses EmptyTree and NodeTree represent the two different kinds of tree 3. we add a fourth attribute empty of type boolean which tells us whether the tree is empty There are probably other options. The first solution is horrible, as every Java programmer knows, because it leads to many NullPointerExceptions. The second solution is probably the most objectorientedly correct but people find it impractical, as it spreads code around in two classes. When I taught java I lectured the third solution, but that one has the big disadvantage that the programmer is responsible for checking every time whether a tree is empty or not. A decent programming language should help with the following common problems regarding binary trees: 1. Prevent the construction of an invalid tree, such as one with missing parts, or dangling pointers.

Page 7 of 16

2. Prevent at compile time access to a component which is not there. For example, the compiler should detect the fact that the programmer is trying to access the left subtree of the empty tree. 3. Make sure the programmer never forgets to consider both possibilitiesthe empty tree and the non-empty tree. The above scheme representation does not help with the first problem. A C implementation with pointers would allow dangling pointers. An object-oriented solution typically wont help with the second and the third problems. You might wonder what it is that I want. The answer is that the programming language should have built-in inductive data types, because thats what binary trees are. In Haskell, which has inductive data types, trees are defined directly in terms of their structure: data Tree = Empty | Node Int Tree Tree This expresses the definition of trees directly: a tree is either empty or a node composed of an integer and two trees. Haskell will be able to catch all the common problems listed above. Other languages supporting this sort of definition are ML, Ocaml, F#, and interestingly Visual Prolog (I am told by Wikipedia). We might ask for more. Suppose we wanted to implement binary search trees. Then we would require that the left subtree only contains nodes that are smaller than the root, and the right subtree only nodes that are larger than the root. Can a programming language be designed so that this property is guaranteed? Yes, for example the compiler could insert suitable checks into the code so that anomalies are detected during execution as soon as they occur. This might be nice for debugging purposes, but what is production code supposed to do if it discovers an anomalous data structure during its execution? Ignore it? Raise an exception? It is much more useful to know before the program is run that the data structure will never be corrupted. Here we hit against a law of nature: there is no algorithm that would analyze an arbitrary piece of code and determine whether it will only produce valid search trees. It is a fact of life. If you really want to check that your programs are correct you will have to help the compiler. There are excellent tools for doing that, such as Coq and Agdahave a look to see how programmers might develop their code in the future. Confusing definitions and variables A definition binds an identifier to a particular fixed value. A variable or a mutable value is a memory location which holds a value that can be read and changed. These two notions should not be confused. Unfortunately, traditional programming languages only provide variables, so many programmers dont even understand what definitions are. Java tries to fix this with the final declaration, and C++ with the const declaration, but these are not used by programmers as much as they could be (which is a typical sign of dubious design decisions). Using variables instead of definitions is wrong for a number of reasons. First, if the compiler knows which identifiers are bound to immutable values it can optimize the code better. It can, for example, decide to store the value in a register, or to keep around several copies without worrying about synchronization between them (think threaded applications). Second, if we allow the programmer to change a value which is supposed to be constant, then he will do so.

Page 8 of 16

If you observe how variables are typically used, you will see several distinct uses:

often a variable is only assigned to once and is used as an (immutable) definition a variable in a loop or list comprehension ranges over the elements of a list, or a collection of objects a variable stores the current state and is genuinely mutable

Should loop counters be mutable? I think not. Code that changes the loop counter in the body of the loop is confusing and error prone. If you want to fiddle with counters, use the while loop instead. So in two out of three cases we want our variables to be immutable, but the popular programming languages only give us variables. Thats silly. We should design the language so that the default case is an immutable value. If the programmer wants a mutable value, he should say so explicitly. This is just the opposite of what Java and C++ do. An example of a language that is designed this way is ML and ocaml. In Haskell you have to jump through hoops to get mutable values (now I am going to hear it from a monad aficionado, please spare me an unnecessary burst of anger). Out of scope variables I thought I would not have to explain why undefined identifiers are a bad a idea, but the reader in comment 27 explicitly asked about this. If a programmer refers to an undefined name then an error should be reported. Concretely, I claimed that Python should complain about the following definition: def f(n): return i + n What is i? Pythonists will quickly point out that i will be defined later, and how deferred definitions are useful because they allows us to define mutually recursive functions. Indeed, Java and Haskell also accept mutually recursive definitions. But unlike Python they make sure that nothing is missing at the time of definition, whereas Python will only complain when the above function f is used. To be honest, Python kindly displays the correct error message showing that the trouble is with the definition of f. But why should this be a runtime error when the mistake can easily be detected at compile time? Actually, this question leads to a more general question, which I consider next. When should mistakes be discovered? Should programming bugs be discovered by the programmer or by the user? The answer seems pretty clear. Therefore, a language should be designed so that as many programming errors as possible are discovered early on, that is before the program is sent to the user. In fact, in order to speed up development (remember that the development time is expensive) the programmer should be told about errors without having to run the program and directing its execution to the place where the latest code change actually gets executed. This philosophy leads to the design of statically checked languages. A typical feature of such a language is that all the types are known at compile time. In contrast, a dynamically typed languages checks the types during runtime. Java, Haskell and ocaml are of the former kind, scheme, javascript and Python of the latter. There are situations in which a statically checked language is better, for example if youre writing a program that will control a laser during eye surgery. But there are also situations in which maximum flexibility is required of a program, for example programs that are embedded in web pages. The web

Page 9 of 16

as we know it would not exist if every javascript error caused the browser to reject the entire web page (try finding a page on a major web site that does not have any javascript errors). Let me also point out that testing cannot replace good language design. Testing is very important, but it should be used to discover problems that cannot be discovered earlier in the development cycle. I used to think that statically checked languages are better for teaching because they prevent the students from doing obviously stupid things. About two years ago I changed my mind. The students learn much better by doing stupid things than by being told by an oppressive compiler that their programs are stupid. So this year I switched to Python. The students are happier, and so am I (because I dont have to explain that code must be properly indented). Python does not whine all the time. Instead it lets them explore the possibilities, and by discovering which ones crash their programs they seem to understand better how the machine works. Difference between OOP and POP OOPs: It is the mechanism that binds together code and data in manipulates, and keeps both safe from outside interference and misuse. In short it isolates a particular code and data from all other codes and data. A well-defined interface controls the access to that particular code and data Object oriented programming organizes a program around it's data i.e objects and a set of well defined interface to that data. An Object-oriented program can be characterized as data controlling access to code. POPs: The procedure oriented programming characterizes a program as a series of user-defined functions
Given problem (verbal/written description of a problem) is split into more manageable modules Then design a number of data structures that hold our data and we implement a number of functions to operate on this data. These functions would modify the data structures, store them to files and also print them So,

all knowledge about the system is built into a set of functions The prime focus is on these functions which is the style of procedure oriented programming **The procedure oriented programming (POP) approach focuses on creating and ordering procedures or a block of code keeping in mind to accomplish a specific job. The key features of this kind of approach are: use of procedures, sequencing of procedures and sharing global data Object Oriented Programming (OOP) has brought a new era in programming world while we were familiar with Procedure Oriented Programming (POP). To do well in programming we should have sound idea about two of them. In the next we will try to distinguish among OOP and POP. The basic difference between Procedure Oriented Programming and Object Oriented Programming are as follows:

Procedure Oriented Programming (POP) 1. Main program is divided into small parts depending on the functions. 2. The Different part of the program connects with each other by parameter passing & using operating system. 3. Every function contains different data. 4. Functions get more importance than data in program. 5. Most of the functions use global data. 6. Same data may be transfer from one function to another 7. There is no perfect way for data hiding. 8. Functions communicate with other functions maintaining as usual rules. 9. More data or functions can not be added with program if necessary. For this purpose full program need to be change.

Page 10 of 16

10. To add new data in program user should be ensure that function allows it. 11. Top down process is followed for program design. 12. Example: Pascal, Fortran Object Oriented Programming (OOP) 1. Main program is divided into small object depending on the problem. 2. Functions of object linked with object using message passing. 3. Data & functions of each individual object act like a single unit. 4. Data gets more importance than functions in program. 5. Each object controls its own data. 6. Data does not possible transfer from one object to another. 7. Data hiding possible in OOP which prevent illegal access of function from outside of it. This is one of the best advantages of OOP also. 8. One object link with other using the message passing. 9. More data or functions can be added with program if necessary. For this purpose full program need not to be change. 10. Message passing ensure the permission of accessing member of an object from other object. 11. Bottom up process is followed for program design. 12. Example: C++, Java. POP: 1 Importance is given to the sequence of things to be done. i.e algorithms 2 Larger programs are divided into functions. 3 Mostly functions share global data i.e data move freely around the system from function to function. 4 Adding of data and function is difficult. 5 No access specifier. 6 Operator cannot be overloaded. 7 Top-down approach. 8 Basically depends on data directly. 9 Less security. 10 No perfect way for data hiding. EX:-C,Pascal,FORTRAN. OOPS:1 Importance is given to the data. 2 Larger programs are divided into objects. 3 mostly the data is private. 4 Adding of data and function is easy. 5 There are public,private,protected specifiers. 6 Operator can be overloaded. 7 Bottom-up and to-down Approaches. 8 Not directly depends on data. 9 more Security. 10 Data hiding is possible. Ex:- C++,Java

Page 11 of 16

Parameter Passing Methods Procedural abstraction Parameter passing methods pass by value pass by result pass by value-result pass by reference aliasing pass by name Procedures/functions as arguments Procedures Modularize program structure Argument: information passed from caller to callee (actual parameter) Parameter: local variable whose value (sometimes) is received from caller (formal parameter) Procedure declaration name, formal parameters, procedure body withlocal declarations and statement list, optional result type void translateX(point *p, int dx) Parameter Association Positional association Arguments associated with formals one-by-one E.g., C, Pascal, Scheme, Java Keyword association E.g., Ada uses a mixture procedure plot (x,y: in real; penup: in boolean) . plot (0.0, 0.0, penup=> true) .plot (penup=>true, x=>0.0, y=>0.0) Parameter Passing Modes pass by value C, Pascal, Ada, Scheme, Algol68 pass by result Ada pass by value-result (copy-in, copy-out) Fortran, sometimes Ada pass by reference Fortran, Pascal var params, sometimes Cobol pass by name (outmoded) Algol60 Pass by Value { c: array [1..10] of integer; m,n : integer; procedure r (k,j : integer) begin k := k+1; j := j+2; end r; m := 5; n := 3;

Page 12 of 16

r(m,n); write m,n; } Output: 53 By Value: kj 53 Pass by Value Advantages Argument protected from changes in callee Disadvantages Copying of values takes execution time and space, especially for aggregate values Pass by Result { c: array [1..10] of integer; m,n : integer; procedure r (k,j : integer) begin k := k+1; j := j+2; end r; m := 5; n := 3; r(m,n); write m,n; } Error in procedure r: cant use parameters which are uninitialized! Pass by Result Assume we have procedure p(k, j : int) with k and j as result parameters. what is the interpretation of p(m,m)? Assume parameter k has value 2 and j has value 3 at end of p. What value is m on return? Pass by Value-Result { c: array [1..10] of integer; m,n : integer; procedure r (k,j : integer) begin k := k+1; j := j+2; end r; m := 5; n := 3; r(m,n); write m,n; } By Value-Result kj

Page 13 of 16

53 65 Output: 65 Pass by Value-Result { c: array [1..10] of integer; m,n : integer; procedure r (k,j : integer) begin k := k+1; j := j+2; end r; /* set c[m] = m */ m := 2; r(m, c[m]); write c[1], c[2], , c[10]; } What element of c has its value changed? c[2]? c[3]? kj 22 34 6 Pass by Reference { c: array [1..10] of integer; m,n : integer; procedure r (k,j : integer) begin k := k+1; j := j+2; end r; m := 5; n := 3; r(m,n); write m,n; } mn 53 65 kj --> m -->n Value update happens in storage of the caller while callee is executing Comparisons Value-result Has all advantages and disadvantages of value

Page 14 of 16

and result together Reference Advantage: is more efficient than copying Disadvantage: can redefine constants r(0, X) will redefine the constant zero in old Fortran66 compilers Leads to aliasing: when there are two or more different names for the same storage location Side effects not visible from code itself 7 Aliasing: by Reference { y: integer; procedure p(x: integer) { x := x + 1; x := x + y; } y := 2; p(y); write y; } y2 3 6 x -->y during the call, x and y are the same location! output: 6 No Aliasing: Value-Result { y: integer; procedure p(x: integer) { x := x + 1; x := x + y; } y := 2; p(y); write y; } x2 3 5 y2 5 output: 5 8 Another Aliasing Example { j, k, m :integer;

Page 15 of 16

procedure q( a, b: integer) { b := 3; m := m *a; } ... s1: q(m, k); s2: q(j, j); } global-formal aliases: <m,a> <k,b> associations during call S1; formal-formal aliases: <a,b> during call S2; Pass by Reference Disadvantage: if an error occurs, harder totrace values since some side-effected values are in environment of the caller What happens when someone uses an expression argument for a by reference parameter? (2*x)?? 9 Pass by Name { c: array [1..10] of integer; m,n : integer; procedure r (k,j : integer) begin k := k+1; j := j+2; end r; /* set c[n] to n */ m := 2; r(m,c[m]); write m,n; } m:= m+1 m c[ ] 2 1 2 3 4 5 6 7 8 9 10 3 c[m] := c[m] + 2 1 2 5 4 5 6 7 8 9 10 Pass by Name Algol60 device Deferred calculation of the argument until needed; like textual substitution with name clashes resolved THUNK - evaluates argument in callers environment and returns address of locationcontaining the result Characteristics Inefficient

Page 16 of 16

Same as pass by reference for scalars 10 Procedures as Parameters To type check the call, need the full function signature of the function argument <function name> : <vector of parameter types> <return type> e.g., translateX:(point *, int) void procedure q( x: integer; function s (y,z: integer):integer) s takes 2 integer arguments and returns an integer! Example { m, k : integer; procedure q(x : integer; function s(y,z: integer): integer) { k, l : integer; s(); /*call to function parameter s */ } /* end of q*/ integer function f(w,v: integer) { w := k*v; /* which k is this? k or k?*/ } q(m, f); }

You might also like