0% found this document useful (0 votes)
6 views

Principle of Programming  Languages.

The document outlines the syllabus for the BCA Semester 2 course on Principles of Programming Languages, including an introduction to programming language design, compilation vs interpretation, and programming environments. It emphasizes the importance of understanding different programming paradigms and their features for effective software development. The document also details the historical development of programming languages and their evolution in response to technological advancements and programming needs.

Uploaded by

sethinupoor931
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views

Principle of Programming  Languages.

The document outlines the syllabus for the BCA Semester 2 course on Principles of Programming Languages, including an introduction to programming language design, compilation vs interpretation, and programming environments. It emphasizes the importance of understanding different programming paradigms and their features for effective software development. The document also details the historical development of programming languages and their evolution in response to technological advancements and programming needs.

Uploaded by

sethinupoor931
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 186

BCA SEMESTER 2

❇ Click the link below to join the BCA


Batch-8 Main Community:
(https://chat.whatsapp.com/
DgVLGa24pLNJpc2Awnmzul)

DCA1209
PRINCIPLE OF PROGRAMMING
Unit: 1 - Introduction to Principle ofLANGUAGES
Programming Languages 1
DCA1209: Principle of Programming Languages

❇ Click the link below to join the BCA


Batch-8 Main Community:
(https://chat.whatsapp.com/
DgVLGa24pLNJpc2Awnmzul)

Unit – 1
Introduction to Principle of
Programming Languages

Unit: 1 - Introduction to Principle of Programming Languages 2


DCA1209: Principle of Programming Languages

TABLE OF CONTENTS

Fig No /
SL SAQ / Page
Topic Table /
No Activity No
Graph
1 Introduction - -
4
1.1 Objectives - -
2 Programming Language Design - -
Program performance and features of i -
2.1
programming languages 5 - 14
2.2 Development of programming languages - -

2.3 Structures of Programming Languages - -


3 Compilation vs Interpretation - -

3.1 Compilation - -

3.2 Compilation Stages 1 -

3.3 Types of Compilers 2, 3, 4, 5, 6, 7 - 15 - 33

3.4 Interpretation 8 -

3.5 Types of Interpreters - -

3.6 Compiler vs Interpreter ii -


4 Programming Environments - -

4.1 IDE - - 34 - 36

4.2 Features and Tools Included in IDEs - -


5 Summary - - 37
6 Self-Assessment Questions - 1 38 - 39
7 Glossary - - 40
8 Terminal Questions - - 41
9 Answers - - 42
10 References - - 43

Unit: 1 - Introduction to Principle of Programming Languages 3


DCA1209: Principle of Programming Languages

1. INTRODUCTION
Studying the fundamental concepts of programming languages allows learners to produce clearer and
more optimised code and the flexibility to adjust to new languages quickly. This enhances their
problem-solving and debugging skills. It offers a more complete outlook on software development,
promoting creativity and innovation. Acquiring this expertise is essential for enhancing one's
professional growth and is the basis for more advanced computer science subjects. Knowledge of
different paradigms and linguistic features enhances the ability to make informed choices in software
design and optimisation.

In this unit, learners will study Programming language design, which involves creating languages with
qualities like readability, writability, reliability, and efficiency. Compilation is translating high-level
code into machine code before execution, whereas interpretation involves executing code line by line
without requiring prior translation. Programming environments encompass a variety of tools such as
IDEs, command-line utilities, text editors, and debuggers, all of which aid in the software development
process. These fundamental principles assist developers in selecting suitable tools and programming
languages for specific jobs and producing superior code.

To thoroughly understand the topic, learners must understand the basic principles, engage in hands-
on exercises using both compiled and interpreted languages and investigate different programming
environments. They must also participate in practical projects, use debuggers and profilers, and
interact with discussion boards.

1.1. Objectives
After studying this unit, you should be able to:

• Explain the overview of different programming


languages and its background.
• Explain the structures of programming
languages.
• Describe the different programming
environments.

Unit: 1 - Introduction to Principle of Programming Languages 4


DCA1209: Principle of Programming Languages

2. PROGRAMMING LANGUAGE DESIGN


Numerous programming languages have been created, with several thousand currently in active
utilisation. Programming languages have a higher degree of similarity to each other when compared
to natural languages that have arisen and evolved independently. The reason behind this is:

• Various programming languages, such as Boolean algebra and logic, are built upon a common
mathematical basis.
• They offer comparable capabilities like arithmetic, logic operations, and text processing.
• They are founded on identical hardware and instruction sets.
• Both of them share the same design objectives: to identify programming languages that are
user-friendly and resource-efficient.
• Programming language designers exchange their design experiences.

Some programming languages show greater similarities, while others display greater differences.
Programming languages can be classified into distinct categories based on their similarities or
paradigms. In programming languages, a paradigm refers to a collection of fundamental rules,
concepts, and techniques that dictate how a computation or algorithm is articulated. The primary
paradigms include imperative, object-oriented, functional, and logic paradigms.

The imperative programming paradigm, or the procedural programming paradigm, involves


performing computations by precisely manipulating named data in a controlled manner, following a
step-by-step approach. Put simply, data or values are initially placed in variables, retrieved from
memory, processed in the arithmetic logic unit (ALU), and then saved again in the same or different
variables. Ultimately, the values of variables are transmitted to the input/output devices as output.
The fundamental basis of imperative languages is in the organisation and architecture of computer
hardware, namely the concept of a stored program and the von Neumann machine. Common
imperative programming languages encompass assembly languages and older high-level languages
such as Fortran, Algol, Ada, Pascal, and C.

The object-oriented programming paradigm is essentially similar to the imperative paradigm, except
that it organises related variables and processes into classes of objects. Access privileges for variables
and methods in objects can be configured to streamline the interaction between objects. Objects are
fundamental components of programmes that enable language features such as inheritance, class

Unit: 1 - Introduction to Principle of Programming Languages 5


DCA1209: Principle of Programming Languages

hierarchy, and polymorphism. Examples of object-oriented programming languages are Smalltalk,


C++, Java, and C#.

The functional programming paradigm, also known as the applicative programming paradigm,
represents computation using mathematical functions. Given that computing is often represented
using mathematical functions in various mathematics courses, functional programming is expected
to be comprehensible and straightforward to utilise. Nevertheless, due to the stark contrast between
functional programming and imperative or object-oriented programming, transitioning from the
latter to the former can be challenging for programmers who are accustomed to developing code in
imperative or object-oriented paradigms. In functional programming languages, the key distinction
is in the absence of the concept of memory locations. Each function will accept a set of values as input
(parameters) and generate a solitary output value (function's return value). The resultant value
cannot be kept for future utilisation. It must be utilised either as the ultimate result or promptly as
the parameter value of another function. Functional programming involves the process of defining
functions and structuring the output values of one or more functions as the input parameters of
another function. Common examples of functional programming languages are ML, SML, and
Lisp/Scheme.

The logic programming paradigm, also known as the declarative programming paradigm, represents
computation using logic predicates. A logic programme consists of a collection of facts, rules, and
queries. The execution phase of a logic programme involves systematically comparing a question with
each fact and rule in the provided fact and rule base. If the question is matched, we receive an
affirmative answer to the query. Alternatively, we do not receive a response to the query. Logic
programming involves the process of discovering factual information, establishing rules based on
these facts, and formulating queries to articulate the problems we aim to resolve. Prologue stands as
the sole prominent logic programming language.

It is important to acknowledge that numerous languages are classified under multiple paradigms. As
an example, we may say that C++ is a computer language that follows the principles of object-oriented
programming. Nevertheless, C++ encompasses nearly all the characteristics of C, making it an
imperative programming language as well. C++ can be utilised for writing programmes in the C
programming language. Java is predominantly object-oriented, however it also has certain imperative
characteristics. For example, Java's primitive type variables do not acquire memory from the language
heap like other objects.

Unit: 1 - Introduction to Principle of Programming Languages 6


DCA1209: Principle of Programming Languages

2.1. Program performance and features of programming languages


The elements of a programming language include orthogonality or simplicity, the presence of control
structures, data types and data structures, syntactic design, support for abstraction, expressiveness,
type equivalence, strong versus weak type checking, exception handling, and restricted aliasing.

The effectiveness of a program, encompassing its dependability, clarity, ease of writing, capacity to be
reused, and efficiency, is mostly influenced by the programmer's approach to designing the algorithm
and choosing the appropriate data structures, along with other implementation specifics. The
features of the programming language are essential for supporting and ensuring that programmers
use the appropriate language mechanisms when developing algorithms and data structures. Table 1.1
shows how the characteristics of a language affect the efficiency of a programme developed in that
language.

The table demonstrates that simplicity, control structures, data types, and data structures
substantially influence all elements of a performance. Readability, reusability, writability, and
reliability are all enhanced by syntax design and the use of abstraction. However, they do not exert an
important impact on the efficiency of the programme. Expressiveness enhances the ability to create
code, but it can compromise the program's reliability. Strong type checking and restricted aliasing
limit the flexibility of creating programmes but are widely regarded as yielding more dependable
programmes. Exception handling is a mechanism that protects the programme from abruptly
terminating when it encounters unforeseen situations or semantic problems.

Performance Language Efficiency Readability / Writability Reliability


features Reusability
Simplicity/Orthogonality
Control structures
Typing and data structures
Syntax design
Support for abstraction
Expressiveness
Strong checking
Restricted aliasing
Exception handling

Table 1.1: The influence of language elements on programme performance.

Unit: 1 - Introduction to Principle of Programming Languages 7


DCA1209: Principle of Programming Languages

2.2. Development of programming languages


Hardware and compiler technology advancements and the demand for high-performance
programmes prioritising reliability, readability, writability, reusability, and efficiency have shaped
programming languages.

High
Efficiency Readability Reusability Writability Reliability Performance
Programs

• Efficiency: Improvements in hardware performance have increased demands for software


efficiency, prompting programming languages to enhance resource utilisation and execution
speed. Contemporary programming languages utilise compiler optimisations, runtime
performance improvements, and concurrency techniques to optimise programme efficiency while
minimising resource consumption.
• Readability: Code readability has gained significance as software projects have become more
complex and working together has become more common. Contemporary programming
languages prioritise human understanding by providing unambiguous and expressive syntax,
significant naming conventions, and clearly defined code structures. This enhances developers'
understanding, maintenance, and troubleshooting processes.
• Reusability: Due to the widespread availability of software components and libraries, reusability
has become an essential component of software development efficiency. Programming languages
facilitate reusability by incorporating properties such as modularity, encapsulation, inheritance,
and polymorphism. These qualities enable developers to efficiently use existing code components
to instantly construct new programs.
• Writability: Developers need languages that allow them to express their concepts simply and
effectively, without unnecessary detail or intricacy. Language features such as high-level
abstractions, compact syntax, and strong libraries improve the ease of writing code by enabling
programmers to express ideas clearly and efficiently.
• Reliability: The increasing complexity of hardware has led to a significant increase in the need
for dependable software. Programming languages have evolved by integrating features like robust

Unit: 1 - Introduction to Principle of Programming Languages 8


DCA1209: Principle of Programming Languages

error-handling methods, automated memory management (e.g., garbage collection), and strong
type systems to reduce the occurrence of defects and runtime errors.

Early programming languages were designed to closely resemble machine language due to the
constraints imposed by hardware and compilers. Machine languages are the primary languages of
computers and the initial set of programming languages employed by humans to interact with
computers.

Machine languages are comprised of instructions represented by binary integers, which can be
challenging for humans to memorise. Subsequent to programming language development, the
utilisation of mnemonics emerges as the subsequent phase, enabling the representation of commonly
used bit patterns through specific symbols. Assembly language refers to a machine language that
incorporates advanced mnemonics. An assembly language typically supports basic variables,
branching to a specific address, several addressing modes, and macros that can express multiple
instructions. An assembler is a tool that converts an assembly language programme into a machine
language programme. An assembler's primary task is to convert mnemonic symbols into their
corresponding binary representations, replace variables with register numbers or memory locations,
and determine the destination address for branch instructions based on the labels' positions in the
programme.

The origin of the first high-level programming language can be attributed to Konrad Zuse's Plankalkül
programming system, developed in Germany in 1946. Zuse created the Z1, Z2, Z3, and Z4 computers
throughout the late 1930s and early 1940s. The Z4 machine, located at ETH (Eidgenössisch
Technische Hochschule) in Zürich, was used to construct the Plankalkül system. Zuse utilised this
system to design a programme capable of playing chess.

The inaugural high-level programming language that was practically employed in an electronic
computer system was formulated in 1949. The language was designated as Short Code. There was a
lack of a compiler specifically developed for the language, necessitating the manual compilation of
programmes written in the language into machine code.

Grace Hopper is credited with inventing the compiler, as she built the first widely recognised compiler,
known as A0, in 1951.

Alick E. Glennie developed the initial version of Autocoder, which is considered the first primitive
compiler, in 1952. It converted Autocode programmes, which were written in symbolic statements,

Unit: 1 - Introduction to Principle of Programming Languages 9


DCA1209: Principle of Programming Languages

into machine language for the Manchester Mark I computer. Autocode has the capability to process
identifiers consisting of a single letter and perform calculations using basic formulas.

Fortran (FORmula TRANslating) was the inaugural widely adopted programming language created
by John Backus and his colleagues at IBM from 1954 to 1957. Backus was, moreover, the co-designer
of the IBM 704, which operated the inaugural Fortran compiler. Later, Backus participated in creating
the Algol programming language and the Backus-Naur Form (BNF). BNF, short for Backus-Naur Form,
served as a formal notation for precisely specifying the syntax of programming languages. Fortran II
was introduced in 1958. Fortran III was developed in late 1958, but it was not made available to the
general public. Subsequent iterations of Fortran include ASA Fortran 66 (Fortran IV) in 1966, ANSI
Fortran 77 (Fortran V) in 1978, ISO Fortran 90 in 1991, and ISO Fortran 95 in 1997. In contrast to
assembly languages, the initial iterations of Fortran permitted the usage of several variable types
(such as real, integer, and array), supported procedure calls, and incorporated basic control
structures.

Programmes produced in programming languages before the development of organised


programming concepts were commonly referred to as spaghetti programming or monolithic
programming.

Structured programming is a method for arranging programmes in a hierarchical structure of


modules. Every module had only one entry and one exit point. Control was transferred downwards
within the structure without any unconditional branching, such as using the "goto" statement to
higher system levels. The programme employed only three control structures: sequential, conditional
branch, and iteration.

Algol 58 was announced in 1958, after the experience gained from Fortran I. Two years later, Algol
60, the initial block-structured language, was introduced. The language underwent revisions in 1963
and 1968. Edsger Dijkstra has been recognised as the creator of the initial Algol 60 compiler. He
gained renown for his leadership in pioneering structured programming and for his efforts to
eliminate the use of the goto statement in programming.

Pascal, a programming language, was created by Niklaus Wirth between 1968 and 1970 and has its
origins in Algol. In 1977, he made significant advancements to Modula, which was intended to replace
Pascal. He subsequently went on to develop Modula-2 in 1980, and Oberon in 1988. The Oberon
language included a syntax similar to Pascal, but it had a powerful typing system. Additionally, it

Unit: 1 - Introduction to Principle of Programming Languages 10


DCA1209: Principle of Programming Languages

provided type extension (inheritance) that facilitated object-oriented programming. Oberon-2


introduced type-bound procedures, similar to object-oriented programming language methods.

Dennis Ritchie developed and first implemented the C programming language at DEC from 1969 to
1973. It was designed as a system implementation language for the early Unix operating system. It
quickly emerged as one of the prevailing languages throughout that era and continues to hold that
position today. The antecedents of C were the typeless language BCPL (Basic Combined Programming
Language) developed by Martin Richards in 1967, followed by the creation of B by Ken Thompson in
1969. The C programming language employs a weak type checking system in order to facilitate a
greater degree of programming flexibility.

The Simula language, developed by Ole-Johan Dahl and Kristen Nygaard at the Norwegian
Computing Centre (NCC) from 1962 to 1967, was the first to introduce and implement object-oriented
programming concepts. Simula I was initially developed as a programming language specifically for
simulating discrete events. Yet, Simula 67, its updated iteration, emerged as a comprehensive and
versatile programming language. Despite its limited popularity, Simula had a significant impact on
the development of modern programming paradigms. It presented significant object-oriented
principles such as classes and objects, inheritance, and late binding.

Smalltalk, developed at Xerox PARC under the leadership of Alan Kay, is one of the object-oriented
programming languages that followed Simula. The versions that were created encompassed
Smalltalk-72, Smalltalk-74, Smalltalk-76, and Smalltalk-80. Smalltalk also acquired functional
programming characteristics from Lisp.

C++ was created by Bjarne Stroustrup in 1980 at Bell Labs as an extension of the languages Simula
67 and C. Originally known as "C with classes," it was later improved and renamed as C++ in 1983.
C++ was seen as an improved version of C, with robust type checking and incorporating data
abstraction and object-oriented programming principles acquired from Simula 67.

Java was authored by James Gosling, Patrick Naughton, Chris Warth, Ed Frank, and Mike Sheridan at
Sun Microsystems. The programming language was initially named Oak, but it was later changed to
Java when it was officially announced to the public in 1995. Java's antecedents were C++ and Smalltalk.
Java eliminated the majority of non-object-oriented characteristics from C++ and emerged as a more
streamlined and superior object-oriented programming language. The language's dominance in
creating Internet applications can be attributed to its two-level programme processing idea. This
concept involves compiling the code into an intermediate bytecode and then interpreting the

Unit: 1 - Introduction to Principle of Programming Languages 11


DCA1209: Principle of Programming Languages

bytecode using a small virtual machine. Java remained an impure object-oriented programming
language. The primitive kinds, such as integers, floating-point numbers, and Booleans, were not
implemented as classes. Instead, their memory allocations were made from the language stack rather
than the language heap.

The announcement of Microsoft's C# language took place in June 2000. This language is a derivative
of both C++ and Java. The language was implemented in a fully object-oriented manner, without
needing "primitive" types. C# also prioritises component-oriented programming, an enhanced
iteration of object-oriented programming. The concept is to have the capability to construct software
systems by utilising premanufactured components.

2.3. Structures of Programming Languages


The structures of programming languages have four distinct layers:
i. lexical,
ii. syntactic,
iii. contextual, and
iv. semantic.

Lexical Syntactic

Contextual Semantic

i. Lexical Structure: The lexical organisation of a language determines its vocabulary. Lexical units
are regarded as the fundamental components of programming languages. The lexical structures
of programming languages are typically analogous and typically encompass the following types of
entities:
• Identifiers: Programmer-selected names used to represent objects such as variables, labels,
procedures, and functions. Typically, computer languages mandate that an identifier must
commence with an alphabetic character and may optionally be succeeded by alphanumeric
characters and certain special symbols.

Unit: 1 - Introduction to Principle of Programming Languages 12


DCA1209: Principle of Programming Languages

• Keywords: Reserved names designated by the language designer and utilised to construct the
syntactic framework of the language.
• Operators: Symbols Used to denote the operations. Every general-purpose programming
language should include essential operators, such as mathematics operators (+, −, *, /),
relational operators (<, , ==, >, ), and logic operators (AND, OR, NOT, etc.).
• Separators: Symbols employed to delimit lexical or syntactic elements of the language. Space,
commas, colon, semicolon, and brackets serve as separators.
• Literals: Values that can be assigned to variables of various sorts. For instance, integer-type
literals refer to integer values, character-type literals encompass any character from the
language's character set, and string-type literals encompass any sequence of characters.
• Comments: Any descriptive text included within the programme. Comments are initiated by a
designated keyword or separator. The compiler disregards any comments when translating a
programme into machine code.

ii. Syntactic structure


Syntactic structure refers to the rules and principles that govern the formation of sentences or
statements using the individual words or phrases that make up a language. An imperative
programming language often provides the following types of statements:
• Assignments: An assignment statement assigns a certain value or expression to a variable.
• Conditional statements: A conditional statement evaluates a condition and directs the
programme to a certain statement depending on whether the condition is true or not. Common
conditional formulations include if-then, if-then-else, and switch (case).
• Loop statements: A loop statement evaluates a condition and either enters the loop body or
exits the loop depending on the evaluation result (true or false). Common loop constructs
include the for-loop and the while-loop.

iii. Contextual structure


Contextual structure, also known as static semantics, establishes a program's meaning prior to its
dynamic execution. The process encompasses the declaration of variables, their initialisation, and
the verification of their types.

In many imperative programming languages, initialising variables at the time of declaration


within the contextual layer is mandatory. However, other languages do not impose this need, as
long as the variables are initialised before their values are utilised. Initialisation can be performed
at either the contextual or semantic layer.

Unit: 1 - Introduction to Principle of Programming Languages 13


DCA1209: Principle of Programming Languages

Contextual structure relates to the interpretation of the program's meaning. A sentence that is
grammatically ((lexically) correct may not be semantically (contextually) correct.

Example:
stringstr = "hello";
int i = 0;
int j = i + str;
In the above example, all declaration statements are syntactically correct. However, the last one
is semantically incorrect because it is not meaningful when combining an integer variable with a
string variable.

iv. Semantic structure


The semantic structure of a program refers to its fundamental significance and the actions it
performs when executed. The meaning and interpretation of a language are frequently complex.
Most declarative languages lack a thorough description of semantic structure. Typically, informal
descriptions are included to explain the functionality of each statement. The semantic frameworks
of functional and logic programming languages are typically established using the mathematical
and logical principles that form the foundation of these languages. For example, the semantics of
Scheme procedures correspond to the semantics of lambda expressions in lambda calculus, which
serves as the foundation for Scheme. Similarly, the semantics of Prologue clauses align with those
of clauses in Horn logic, which forms the basis for Prologue.

Unit: 1 - Introduction to Principle of Programming Languages 14


DCA1209: Principle of Programming Languages

3. COMPILATION vs INTERPRETATION
3.1. Compilation:
Definition: Compilation is converting source code written in a high-level programming language into
a lower-level language, like machine code, which can be executed by a computer.

Importance of Compilation: Compiling your code is essential as computers can only understand
machine code. Compiling is the process of converting code that is written in a format that humans can
understand into a format that computers can understand and execute.

How it works: During Compilation, the source code is examined, parsed, and transformed into an
intermediate representation known as object code. Subsequently, the object code is combined with other
essential files to generate an executable program.

Compiler: A compiler is a software instrument that executes the compilation process. The system's
input is the source code, which is then processed to produce the output in the form of an executable
program or object code.

3.2. Compilation Stages


The Compiler undergoes several stages to generate the final target's code. This is shown in Figure 1.
The stages are:
• Lexical Analysis
• Syntax Analysis
• Semantic Analysis
• Intermediate Code Generation
• Code Optimization
• Target Code Generation

The initial three stages are commonly called the Analysis Phase, and the final three are called the
Synthesis Phase.

Unit: 1 - Introduction to Principle of Programming Languages 15


DCA1209: Principle of Programming Languages

Figure 1: Stages of Compilation


(Source Image Link: https://www.kttpro.com/wp-content/uploads/2017/02/Compiler-
768x594.png)

i. Lexical Analysis:
Lexical analysis, also known as lexing or scanning, is the first phase of the compilation process in
which the source code is analysed and divided into lexical units known as tokens. The lexical
analysis process examines the input code on a character-by-character basis.

Example: Consider the following line of code:

String name = “Oranges”;

The variable "name" is assigned the value "Oranges".

The lexical analyser would produce 7 tokens, stored as 7 records in the Symbol Table.

In this phase, a string named "name" is assigned the value "Oranges". The Symbol table is then
generated and filled with the tokens that were generated. A symbol table is a commonly used data
structure that stores a record for every identifier found in the source code.

The result of this phase is a stream of Tokens.

Unit: 1 - Introduction to Principle of Programming Languages 16


DCA1209: Principle of Programming Languages

ii. Syntax Analyser:


The syntax analyser is responsible for handling this phase. The stream of tokens produced during
the lexical analysis step is further examined to verify that the input code follows to the syntax of
the specific programming language.

This phase is responsible for detecting syntax mistakes.

The result of this phase is abstract syntax trees.

iii. Semantic analyser:


The Semantic Analysis module is handled by the Semantic Analyses, which is responsible for
verifying that the source code follows to established semantic standards.

This phase is responsible for managing type checking. This guarantees that the variables are
assigned values in accordance with their declaration.

If a variable is declared as an integer and then assigned a float, the Semantic Analyzer will detect
and report the issue.

This stage also detects segments of code, such as operands and operators, within the input code.

The result of this phase is the Parse Tree.

iv. Intermediate Code Generation:


Intermediate code is a code that serves as a bridge between the source code and the target code.
It is a representation of the input source programme. An important characteristic of an
Intermediate Code is its ability to be easily translated into a target programme.

An example can be seen in the compilation of Java programmes into Java Bytecodes, which are
stored as .class files and executed by the Java Virtual Machine. This intermediate code is
compatible with any operating system that supports the Java Virtual Machine (JVM).

The ultimate target code is derived from the intermediate code.

v. Code Optimisation:
Code optimisation involves removing unnecessary code and optimising for efficient memory
management, resulting in improved execution performance. The intermediate code guarantees
the generation of a target code that can be executed on any system, thus facilitating portability
across several platforms.

The result of this phase is the Optimised Code.

Unit: 1 - Introduction to Principle of Programming Languages 17


DCA1209: Principle of Programming Languages

vi. Target Code Generation


The target code is created specifically for the given platform. Machine instructions are derived
from the optimised intermediate code. Variable and register assignment is managed in this section.

The result of this phase is the final/target code.

3.3. Types of Compilers


There are various categories of compilers, and each is tailored to unique objectives. These are some
of the most common types:
i. Single-pass Compilers
ii. Multi-pass Compilers
iii. Just-in-time Compilers (JIT)
iv. Ahead-of-time compilers (AOT)
v. Cross Compilers
vi. Incremental Compilers
vii. Optimising Compilers
viii. Debugging Compilers
ix. Source-to-source compilers

Ahead-of-time
Single-pass Multi-pass Just-in-time
compilers
Compilers Compilers Compilers (JIT)
(AOT)

Cross Incremental Optimising Debugging


Compilers Compilers Compilers Compilers

Source-to-
source
compilers

i. Single-pass Compilers:
Single-pass compilers are a specific sort of compiler that processes the source code and produces
machine code in a single iteration without returning to previous code sections. Typically, they
have higher speed than multi-pass compilers, but they may have lower efficiency in terms of code
quality. Single-pass compilers are advantageous for efficiently handling substantial volumes of
code, particularly when the code is uncomplicated and direct, as shown in Figure 2.

Unit: 1 - Introduction to Principle of Programming Languages 18


DCA1209: Principle of Programming Languages

Working:
Single-pass compilers are a specific form of compiler that is capable of reading and generating the
target code in just one pass over the input. Unlike multi-pass compilers, single-pass compilers do
not require numerous iterations over the source code in order to produce the target code. Single-
pass compilers execute a predetermined sequence of actions to produce the desired target code:

Initially, the source code undergoes a scanning process where individual tokens are recognised
and saved in a data structure known as a symbol table.

Subsequently, the tokens undergo parsing to produce an abstract syntax tree (AST) that
accurately depicts the code's structure.

The target code is generated by the single-pass compiler as the AST is generated. Typically, this
process involves traversing the Abstract Syntax Tree (AST) and producing the desired code for
every node in the tree. Single-pass compilers can also optimise the target code by eliminating
unnecessary instructions and improving memory utilisation.

Ultimately, the compiler generates the target code and stores it either in a file or directly in
memory. Single-pass compilers are beneficial for programming languages with uncomplicated
syntax and do not require several iterations over the source code to produce the target code
quickly. Nevertheless, they may not be appropriate for languages with intricate syntax and require
several iterations over the source code.

Example: Turbo Pascal Compiler

Unit: 1 - Introduction to Principle of Programming Languages 19


DCA1209: Principle of Programming Languages

Figure 2: Single-pass Compiler


(Image Source Link:
https://miro.medium.com/v2/resize:fit:828/format:webp/1*pZw1VSk_O7goqZMwq1GnCQ.p
ng)

ii. Multi-pass Compiler


Multi-pass compilers are a specific kind of compiler that iteratively processes the source code,
improving the result with each iteration. Multi-pass compilers are more complex than single-pass
compilers, but they can generate code that is more optimised and efficient.

Working:
Multi-pass compilers are a specific kind of compilers that analyse and produce the target code by
making numerous passes through the input. Unlike compilers that only do a single pass, these
compilers require numerous passes over the source code in order to build the target code.

In the initial phase of a multi-pass compiler, the source code is scanned and a symbol table is
constructed. This table contains data on variables, functions, and other symbols utilised in the
programme.

Unit: 1 - Introduction to Principle of Programming Languages 20


DCA1209: Principle of Programming Languages

The symbol table is subsequently utilised in later runs to do semantic analysis, which verifies for
mistakes such as type inconsistencies or variables that have not been declared.

During the analysis process, the compiler produces an intermediate representation (IR) of the
programme. This IR is a high-level and abstract representation of the source code.

During succeeding iterations, the multi-pass compiler applies optimisations to the intermediate
representation (IR), including eliminating superfluous instructions, rearranging instructions for
improved efficiency, and optimising memory allocation.

The last stage of the compiler produces the target code by utilising the optimised intermediate
representation (IR). The target code can be in the form of machine code, assembly language, or
any other executable language for the target platform as shown in figure 3.

Example: GCC (GNU Compiler Collection)

Figure 3: Multi-pass Compiler


(Image source link:
https://miro.medium.com/v2/resize:fit:828/format:webp/1*lRYRyXMkjO-
iKtWoHRijBQ.png)

Unit: 1 - Introduction to Principle of Programming Languages 21


DCA1209: Principle of Programming Languages

iii. Just-in-time compilers (JIT):


Just-in-time compilers (JIT) are a type of compiler that translates code into machine language at
runtime, right before it is executed.

Just-in-time (JIT) compilers are a specific kind of compiler that generates machine code during
programme execution instead of translating the complete programme beforehand, as shown in
Figure 4. They are frequently employed in virtual machines, such as those utilised for Java
and .NET, to enhance performance by optimising the code as it is being executed.

Working:
Just-in-time compilers function by compiling code during runtime instead of ahead-of-time
compilers that compile code before execution.

During programme execution, the Just-In-Time (JIT) compiler examines the code and creates
machine code that may be directly executed by the processor.

The JIT compiler initially produces a low-level intermediate code that is optimised for runtime
performance.

Subsequently, the code is compiled dynamically into machine code while the program executes.
The created code is kept in the computer's memory and can be utilised again whenever necessary.

The JIT compiler is capable of performing optimisations that are not feasible with ahead-of-time
compilation since it generates code depending on the current execution context.

For example, it can incorporate function calls within the code, enhance the efficiency of loops, and
remove unnecessary code depending on the program's actual behaviour.

Example: Java Virtual Machine (JVM)

Unit: 1 - Introduction to Principle of Programming Languages 22


DCA1209: Principle of Programming Languages

Figure 4: JIT Compiler


(Image Source Link:
https://miro.medium.com/v2/resize:fit:828/format:webp/1*YtXlk_dCf7z12d0a0_57_Q.png)

iv. Ahead-of-time compilers (AOT)


Ahead-of-time (AOT) compilers are particular compilers that convert the source code into
machine code ahead of programme execution. Unlike JIT compilers, which produce machine code
at runtime, this operates differently. AOT compilers are frequently employed to develop native
apps for desktop and mobile platforms.

Working:
During the initial phase, the compiler scans the source code and conducts a lexical analysis to
recognise the code's fundamental components, including keywords, operators, and identifiers.

The subsequent phase entails parsing the code to generate a parse tree, which depicts the code's
hierarchical structure. Subsequently, the parse tree is employed for semantic analysis, wherein
the code is examined for faults, and the pieces of the code are given significance based on the
context.

After the code has been examined and confirmed, the AOT compiler produces machine code by
employing a code generation method that converts the code into the desired machine language.
The compiler optimises the machine code to enhance performance, size, and efficiency.

The AOT compiler produces an executable file as its result, which may be distributed and run on
the target system without requiring a second compilation process. AOT compilers are frequently
employed in systems characterised by limited resources or strict performance demands, such as

Unit: 1 - Introduction to Principle of Programming Languages 23


DCA1209: Principle of Programming Languages

embedded devices or real-time systems, where the program's initialisation time must be
minimised.

Example: GNU Compiler for Java (GCJ).

Figure 5: AOT Compiler


(Image Source:
https://miro.medium.com/v2/resize:fit:828/format:webp/1*dWbSf4wMZl1kw-
BkfKr7Kw.png)

v. Cross compilers:
A cross-compiler is a compiler that enables you to create code for one platform and generate
executable code for a different platform. The cross-compiler enables code writing and testing on
the development system prior to installing it on the target hardware.

Working:
Cross-compilers are specifically designed to produce executable code for a platform that differs
from the platform on which the compiler itself is operating.

A cross-compiler is a tool that converts source code written in one programming language into an
executable format suitable for running on a different platform.

A cross-compiler utilises a defined collection of target architecture instructions and system


libraries to produce machine code for the intended platform. Subsequently, the code that is
produced is commonly transmitted to the intended platform for implementation.

Cross-compilers are valuable tools for software development in scenarios involving embedded
devices or platforms with restricted resources.

Developers are able to write code on a high-performance computer and then produce optimised
code for the desired platform without needing extra hardware or development resources.

Unit: 1 - Introduction to Principle of Programming Languages 24


DCA1209: Principle of Programming Languages

Example: ARM Compiler

Figure 6: Cross Compiler


(Image Source Link: https://miro.medium.com/v2/resize:fit:828/format:webp/1*YG-
1ECf0EFRo1j2hAooSgg.jpeg)

vi. Incremental Compilers


Incremental compilers are a specific kind of compiler that exclusively compile the sections of a p
rogramme that have been altered or added since the previous compilation.

This enables expedited compilation durations and enhanced utilisation of system resources, as t
he compiler does not necessitate recompiling the entire programme whenever a
modification is implemented.

Working:
• The process of incremental compilation generally consists of multiple sequential steps:
• Dependency analysis is a process in which the compiler examines the relationships between
source files to identify which files require recompilation.
• Incremental compilation refers to the process of recompiling only the updated source files and
any other files that depend on them.
• Linking: The recently compiled code is combined with the preexisting code to generate the
ultimate executable or library.
• Incremental compilers can significantly accelerate the development process and decrease the
time needed for testing and deployment by compiling only the updated code.

Example: TypeScript compiler.

Unit: 1 - Introduction to Principle of Programming Languages 25


DCA1209: Principle of Programming Languages

Figure 7: Incremental Compilers


(Image Source Link:
https://miro.medium.com/v2/resize:fit:828/format:webp/1*2gC4hAZl_fJFwtFnyZivJQ.jpeg)

vii. Optimising Compilers


Optimising compilers are a specific kind of compiler that examines a programme's source code
and modifies the resulting machine code to enhance its efficiency. This is achieved by
implementing different optimisation techniques on the code, such as loop unrolling, inlining, and
constant folding, to decrease the number of executed instructions and minimise memory accesses.

Working:

Optimising compilers employ a combination of static and dynamic analysis approaches to detect
sections of the code that can be enhanced.

Static analysis is examining the code without executing it, and dynamic analysis involves profiling
the code at runtime to detect performance bottlenecks.

This could involve reorganising the code to minimise memory access, reducing unnecessary
processes, and implementing other optimisation techniques.

Afterwards, the compiler produces optimised machine code that executes more efficiently and
consumes fewer system resources compared to unoptimized code.

Example: LLVM Compiler Infrastructure

Unit: 1 - Introduction to Principle of Programming Languages 26


DCA1209: Principle of Programming Languages

viii. Debugging Compilers


Debugging compilers are a specific type of compiler that produces machine code to identify and
diagnose faults in a program. This is achieved by incorporating debugging information into the
machine code, which debuggers can then use to track the program's execution and identify the
origin of any issues.

Working:
Debugging compilers are specifically engineered to assist developers in detecting and rectifying
faults in their code.

They function by including supplementary data into the compiled code, so enabling more
efficient debugging.

When a developer builds their code using a debugging compiler, the resulting executable file has
extra metadata that provides information about the source code and the program's internal state.

Debugging tools can utilise this metadata to pinpoint fault locations, trace data flow inside the
program, and examine the program's memory and CPU status.

Debugging compilers may incorporate supplementary code that executes error checking and
validation, such as boundary checks and null pointer checks.

This can help detect mistakes during programme execution and offer more comprehensive error
messages that are more accessible to developers.

Example: Gdb compiler.

ix. Source-to-Source Compilers


Source-to-source compilers, (commonly referred to as transpilers or transcompilers) are a
specific sort of compiler that converts code from one programming language to another. They
differ from standard compilers in that they convert source code from one high-level programming
language to another rather than translating it directly into machine code.

Working:

Source-to-source compilers, commonly referred to as transpilers, transform source code from one
computer language into another.

The procedure involves analysing the source code, constructing an abstract syntax tree, executing
code modifications and enhancements, and subsequently producing the corresponding source
code in the desired programming language.

Unit: 1 - Introduction to Principle of Programming Languages 27


DCA1209: Principle of Programming Languages

The initial phase involves understanding the source code and transforming it into a methodical
arrangement that can be effectively handled by the compiler.

This process involves carrying out lexical analysis and syntactic analysis on the code to generate
an abstract syntax tree (AST). After the Abstract Syntax Tree (AST) is generated, the compiler
executes a series of transformations and optimisations to generate code that is equivalent to the
target language.

These modifications may involve altering variable names, substituting functions, and reorganising
control flow statements.

Ultimately, the compiler produces the resulting code, which may be produced and run on the
intended platform.

Example: Clang/LLVM C/C++ compiler.

3.4. Interpretation
Machine code conversion is necessary for all high-level languages to ensure that the computer
understands the programme after receiving the necessary inputs. An interpreter is a software that
converts high-level instructions into machine-level language, line by line. It is different from a
compiler or assembler.
The interpreter in the compiler examines the source code sequentially, analysing each line. If an error
is detected on any line, the execution is halted until the error is rectified. The interpreter's ability to
deliver a line-by-line error makes error repair relatively straightforward. However, the programme
requires a longer duration to complete its execution successfully. The use of interpreters was initially
implemented in 1952 to facilitate programming within the constraints of computers during that era.
It converts source code into a highly efficient intermediate representation and executes it without
delay.

Figure 8: The process of translating in an Interpreter


(Image Source Link:
https://miro.medium.com/v2/resize:fit:828/format:webp/1*oBJrOe0aj5UuHJy9ulcLQA.png)

Unit: 1 - Introduction to Principle of Programming Languages 28


DCA1209: Principle of Programming Languages

3.5. Types of Interpreters


A few types of Interpreters are listed here:
• Byte-code Interpreters
• Threaded Code Interpreters
• Abstract Syntax Tree Interpreters
• Self-Interpreters

Byte-code
Interpreters

Threaded
Self Types of
Code
Interpreters Interpreters
Interpreters

Abstract
Syntax Tree
Interpreters

i. Byte-code Interpreters:
• Bytecode interpreters initially transform source code into bytecode, which is a condensed and
enhanced representation of the source code.
• Bytecode is distinct from machine code, but it is capable of being executed by the interpreter.
• The bytecode interpreters use a combination of a compiler and an interpreter, thereby earning
the name "compreters".
• Every bytecode instruction begins with a single byte, which allows for a maximum of 256
instructions.
• Examples of programming languages include Java, Raku, Python, PHP, Tcl, and more.

ii. Threaded code interpreters


• Threaded code interpreters are similar to bytecode interpreters, but they employ pointers.
• Every instruction in pointer interpreters is a word that serves as a pointer to a function or
sequence of instructions.

Unit: 1 - Introduction to Principle of Programming Languages 29


DCA1209: Principle of Programming Languages

• Unlike interpreters that process bytecode, there are no limitations on the quantity of
instructions that can be executed, provided that there is sufficient memory and address space
accessible.
• Examples of programming languages include Forth, Smalltalk, Lisp, Postscript, and others.

iii. Abstract Syntax Tree Interpreters


• Tree-walking interpreters transform source code into an abstract syntax tree (AST).
• The interpreter subsequently executes the programme by this tree.
• The source code is parsed only once for each sentence.
• The program's structure and the interconnections between statements remain unchanged.
• Tree-walking interpreters offer greater analysis at runtime compared to other interpreters.
• Examples of programming languages include Python, Javascript, Ruby, Rust, and others.

iv. Self Interpreters


• Self-interpreters are a distinct category of interpreters.
• These are interpreters for programming languages that are implemented in a programming
language capable of self-interpretation.
• A self-interpreter is shown by a BASIC interpreter that is written in BASIC.
• Self-interpreters are developed in the absence of a compiler for a particular language.
• To create self-interpreters, one must implement the language within a host language, which
can be a different computer language.
• Some examples of programming languages include Lisp, Perl, Ruby, Smalltalk, and others.

A few interpreters are:


• Python Interpreter: The Python interpreter is a piece of software that processes Python code
and converts it into instructions that can be understood by a computer. The interpreter
sequentially executes the code, processing each line individually. In the event of a coding error,
the interpreter halts execution and presents an error message. Python also features a REPL
(Read-Eval-Print Loop) mode, enabling users to execute code line by line and receive instant
feedback.
• Ruby Interpreter: The Ruby interpreter analyses Ruby code and translates it into executable
machine instructions. The interpreter sequentially executes the code, processing each line
individually. In the event of a coding error, the interpreter halts execution and presents an error

Unit: 1 - Introduction to Principle of Programming Languages 30


DCA1209: Principle of Programming Languages

message. Ruby also features an interactive shell that enables users to execute code line by line
and receive immediate response.
• JavaScript Interpreter: JavaScript is a widely-used scripting language employed in the field of
web development. An interpreter is utilised to run the code. The JavaScript interpreter parses
JavaScript code and translates it into executable instructions that can be understood by the
machine. The interpreter sequentially executes the code, processing each line individually. In
the event of a coding error, the interpreter halts execution and presents an error message.
JavaScript features a console that enables users to execute code sequentially and receive instant
feedback.
• Perl Interpreter: Perl is a programming language that operates at a high level and is commonly
employed in web development, system management, and network programming. The code is
executed using an interpreter. The Perl interpreter processes Perl code and translates it into
instructions that can be understood by a computer. The interpreter executes the code line by
line, and if there is a problem in the code, it stops execution and shows an error message.
• PHP Interpreter: PHP is a widely used server-side programming language used in building
websites. It employs an interpreter to carry out code execution. The PHP interpreter processes
PHP code and translates it into instructions that can be understood by a computer. The
interpreter sequentially executes the code, processing each line individually. In the event of a
coding error, the interpreter halts execution and presents an error message. PHP features an
interactive shell that enables users to execute code line by line and receive instant responses.
• Lisp Interpreter: Lisp is a collection of computer programming languages used for artificial
intelligence research and programming language research. Lisp employs an interpreter to carry
out code execution. The Lisp interpreter parses Lisp code and translates it into machine-
executable instructions. The interpreter sequentially executes the code, processing each line
individually. In the event of a coding error, the interpreter halts execution and presents an error
message. Lisp also features a REPL (Read-Eval-Print Loop) mode, enabling users to execute
code line by line and receive instant feedback.
• Shell Interpreter: Shell scripts are used to automate recurring processes on a computer
system. Shell scripts use an interpreter to execute code. The shell interpreter processes shell
script code and translates it into instructions that can be understood by the processor. The
interpreter sequentially executes the code, processing each line individually. In the event of a
coding error, the interpreter halts execution and presents an error message.

Unit: 1 - Introduction to Principle of Programming Languages 31


DCA1209: Principle of Programming Languages

3.6. Compilation vs Interpretation


Table 1.2: Difference between Compilation vs Interpretation
Compiler Interpreter
Programming Steps:
• Programme Development.
Programming steps:
• The compiler analyses language and generates
• Programme Development.
errors when encountering improper
• An interpreter does not necessitate the linking
statements.
of files or the production of machine code.
• If there are no errors, the Compiler will turn
• Sequential execution of source code
the source code into Machine Code.
statements.
• Integration of many code files into an
executable program.
• Executes a programme at last.
The compiler stores the Machine Language as The Interpreter doesn't keep or save the
Machine Code on discs. Machine Language.
Code that is interpreted tends to have slower
Compiled code executes more quickly than
execution speeds compared to code that is
interpreted code.
compiled.
The Linking-Loading framework is the The Interpretation Model serves as the
fundamental operational framework of the fundamental operational framework for the
Compiler. Interpreter.
The compiler produces an executable file (.exe)
The interpreter does not produce any output.
as its result.
Any modification to the source program Modifying the source programme during
following compilation necessitates the translation does not necessitate retranslating
complete recompilation of the code. the complete code.
Compiler errors are now being presented after
Errors are present in each individual line.
compiling.
By analysing the code in advance, the compiler The Interpreter operates by executing code line
is able to optimise the execution, resulting in i by line, so optimisation is slightly slower than
mproved code performance. compilers.
It does not necessitate the presence of source c The source code is necessary for subsequent ex
ode for subsequent execution. ecution.
The programme is executed only when it has The programme is executed once each line is ex
been fully compiled. amined or assessed.
Compilers frequently require a significant amo By comparison, interpreters require less time
unt of time to analyse the source code. to analyse the source code.

Unit: 1 - Introduction to Principle of Programming Languages 32


DCA1209: Principle of Programming Languages

The CPU utilisation is higher while using a Com


The Interpreter results in lower CPU use.
piler.
Compilers are primarily utilised in the producti Interpreters are primarily utilised in program
on environment. ming and development environments.
The object code is stored indefinitely for future There is no preservation of object code for futu
utilisation. re utilisation.

C, C++, C#, and other programming languages a Python, Ruby, Perl, SNOBOL, MATLAB, andothe
re compiler-based. r programming languages are interpreted.

Unit: 1 - Introduction to Principle of Programming Languages 33


DCA1209: Principle of Programming Languages

4. PROGRAMMING ENVIRONMENTS
A programming environment refers to the set of tools utilised for software development.

Generally, a programming environment combines hardware and software, enabling developers to


create applications.

The key components of Programming Environments are Text Editors, IDEs, Compilers and
Interpreters etc.

Developers commonly operate within integrated development environments, sometimes known as


IDEs.

4.1. IDE
An Integrated Development Environment (IDE) consolidates essential development tools into an
integrated software environment.

An Integrated Development Environment (IDE) often includes, at minimum,

• File system: An Integrated Development Environment (IDE) incorporates a file system that
facilitates the organisation, administration, and traversal of project files and directories.
• Text editor: The text editor is an essential component that allows developers to write and
modify source code.
• Linker: A linker is a software utility that merges multiple built object files into a single
executable or library.
• Compiler: A compiler converts high-level source code into machine code or intermediate code
that can be run by a computer.
• Integrated tools: These tools, such as a debugger and profiler, are included in the IDE to
improve the development workflow.

4.2. Features and Tools Included in IDEs


Integrated Development Environments (IDEs) are complex programmes created to help developers
develop software. They include many capabilities that ease coding, debugging, and project
management.

Unit: 1 - Introduction to Principle of Programming Languages 34


DCA1209: Principle of Programming Languages

• Debugging: Debugging tools in an Integrated Development Environment (IDE) enable


developers to execute their code incrementally to detect and resolve errors using features like
breakpoints, watch variables, etc.
• Syntax highlighting: Syntax highlighting is a technique that uses visual indicators to distinguish
various pieces of source code depending on their syntactical significance.
For example, Colour coding, error highlighting, etc.
• Code Completion: Code completion facilitates developers by predicting and proposing
potential completions for partially entered code like snippets.
• Language support: Integrated Development Environments (IDEs) provide help for many
programming languages, typically using plugins or pre-installed modules.
For example, Django for Python, Spring for Java, etc
• Code search: Code search tools (like Find and Replace) facilitate quick identification and access
to specific code segments inside large code repositories.
• Refactoring: Refactoring tools help reorganise preexisting code to enhance its readability,
manageability, and efficiency without altering its external functionality.
For example, renaming the variables, functions, classes and files.
• Version control: Version control integration allows developers to effectively handle
modifications to their codebase, interact with others, and keep a record of their work.
• Visual programming: Visual programming tools provide a graphical user interface for
constructing and editing programme components. These tools are especially valuable in
specific areas such as game development, user interface design, and data flow applications.

Some widely used programming environments or IDEs are:


• Microsoft Visual Studio (VS Code) - An comprehensive and resilient Integrated Development
Environment (IDE) predominantly utilised for .NET development, but also providing support
for other programming languages via extensions.
• NetBeans - An open-source integrated development environment (IDE) predominantly
utilised for Java programming, with additional support for languages such as PHP, C++, and
HTML.
• Turbo C, C++ - An IDE with historical significance in the field of C and C++ development, known
for its simplicity and efficiency.
• Code::Blocks - IDE specifically intended for C, C++, and Fortran programming, which is freely
available and allows anyone to access and modify its source code.

Unit: 1 - Introduction to Principle of Programming Languages 35


DCA1209: Principle of Programming Languages

• Dreamweaver—Adobe's commercial Integrated Development Environment (IDE) is largely


used for web development and offers support for both design and code perspectives.
• Arduino - IDE specifically designed for the purpose of creating software for Arduino
microcontroller boards.

Example: Workflow in Turbo C IDE


Step 1: Starting a New Project
• Open Turbo C.
• Create a new file (e.g., main.c).

Step 2: Write a code


#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}

Step 3: Save the file.


Save the file using File -> Save As or pressing F2.

Step 4: Compiling the Code


• Compile the code using Compile -> Compile or pressing Alt + F9.
• Check the messages for any syntax errors.

Step 5: Linking
Link the object files using Compile -> Link or pressing Ctrl + F9.

Step 6: Run the program.


Run the executable using Run -> Run or pressing Ctrl + F9.
The output window will display "Hello, World!".

Step 7: Debugging (if required)


Set breakpoints and use Debug -> Start to run the program in debug mode.

Step through the code using Debug -> Step Into (F7) or Step Over (F8).

Unit: 1 - Introduction to Principle of Programming Languages 36


DCA1209: Principle of Programming Languages

5. SUMMARY
The objective of programming language design is to develop languages that enhance programme
performance and include fundamental attributes such as readability, writability, reliability, and
efficiency. Technological breakthroughs and growing software needs have significantly influenced the
development of programming languages. This process involves organising languages into four
distinct layers: lexical, syntactic, contextual, and semantic. These layers control many elements of
code processing and interpretation, ranging from tokenising input to understanding context and
semantics. Efficient programming language design guarantees that the language is simple to
understand to humans, written with high efficiency, and executed accurately in diverse settings,
hence improving total programme performance.

Compilation and interpretation are two key approaches for executing programmes. Compilation is
the process of translating high-level code into machine code. It involves various phases, namely
preprocessing, compiling, assembly, and linking. This procedure generates an executable file that is
capable of being executed on a designated machine. Different categories of compilers, including cross-
compilers and just-in-time compilers, have distinct functions, such as facilitating code execution on
diverse platforms or enhancing runtime performance. Interpretation, on the other hand, processes
code sequentially during runtime, offering instant feedback and fault identification but typically
resulting in slower execution. The decision to use either a compiler or an interpreter is contingent
upon criteria such as the requirement for fast execution, the ability to detect errors, and the
convenience of the development process. Programming environments, particularly Integrated
Development Environments (IDEs), incorporate these procedures by offering tools such as text
editors, compilers, linkers, and debuggers. Integrated Development Environments (IDEs)
significantly improve the productivity of software development by incorporating advanced
functionalities like syntax highlighting, code completion, version control, and visual programming.
These capabilities are essential in contemporary programming processes, making IDEs indispensable
tools. Notable instances of Integrated Development Environments (IDEs) encompass Microsoft Visual
Studio, NetBeans, Turbo C, Code::Blocks, Dreamweaver, and Arduino. Each of these IDEs serves
distinct programming requirements and supports various programming languages.

Unit: 1 - Introduction to Principle of Programming Languages 37


DCA1209: Principle of Programming Languages

6. SELF-ASSESSMENT QUESTIONS
Multiple choice Questions
1 Which of the following is NOT a feature of programming languages?
A) Readability
B) Write-only
C) Reliability
D) Efficiency
2 What is the primary goal of programming language design?
A) Code obfuscation
B) Program performance
C) Increasing bugs
D) Decreasing readability
3 Which type of compiler translates source code into machine code for a different platform
than the one it is running on?
A) Cross-compiler
B) Just-in-time compiler
C) Native compiler
D) Optimizing compiler
4 Which programming environment provides tools like text editors, compilers, and debuggers?
A) IDE
B) CLI
C) GUI
D) API
5 What is the primary purpose of an Integrated Development Environment (IDE)?
A) Playing games
B) Enhancing developer productivity
C) Listening to music
D) Sending emails
6 Which programming language design goal emphasizes the ease of understanding code?
A) Writability
B) Reliability

Unit: 1 - Introduction to Principle of Programming Languages 38


DCA1209: Principle of Programming Languages

C) Readability
D) Efficiency
7 What does the lexical analysis stage of compilation involve?
A) Converting high-level code into machine code
B) Breaking down source code into tokens
C) Optimizing the generated machine code
D) Combining object files into an executable
True or False:

8 Compilation translates high-level code into machine code directly.

9 Turbo C is an example of an interpreted programming language.

10 Compilers and interpreters serve the same purpose in executing programs.

Unit: 1 - Introduction to Principle of Programming Languages 39


DCA1209: Principle of Programming Languages

7. GLOSSARY
Financial Management is concerned with the procurement of the least cost funds, and its effective

Compilation refers to the procedure of converting source code written in


Compilation -
a high-level programming language into machine code or bytecode.

The process of executing code sequentially at runtime without the need


Interpretation -
for compilation beforehand.

Programming environments refer to integrated tools and interfaces that


Programming
- facilitate software development, encompassing integrated development
Environments
environments (IDEs) and text editors.
IDE (Integrated An Integrated Development Environment (IDE) refers to software tools
Development - that offer extensive capabilities for writing, debugging, and project
Environment) management.
A text editor is software for modifying source code. It typically includes
Text Editor - features such as syntax highlighting, code completion, and search
capabilities.

A compiler is a software programme that converts high-level source code


Compiler -
into machine or bytecode.

An interpreter is a software programme that runs code sequentially at


Interpreter -
runtime without requiring prior compilation.

Debugging refers to the systematic process of discovering and rectifying


Debugging -
mistakes or faults in computer code.

A Just-In-Time (JIT) compiler is an essential component of the runtime


Just-In-Time (JIT)
- environment. It converts bytecode or intermediate code into machine
compiler
code right before execution.
An Ahead-Of-Time (AOT) compiler converts high-level code or
Ahead-Of-Time
- intermediate code into machine code before execution, specifically during
(AOT) compiler
the build phase.

Unit: 1 - Introduction to Principle of Programming Languages 40


DCA1209: Principle of Programming Languages

8. TERMINAL QUESTIONS
1. Explain the program performance and features of Programming languages.
2. Elucidate the development of programming languages.
3. Explicate the structures of programming languages.
4. Briefly explain the stages of compilation.
5. Elucidate Single-pass compiler and multi-pass compiler in detail.
6. Differentiate between JIT and AOT.
7. Define Interpreter. Explain its types.
8. Differentiate between Compiler and Interpreter.
9. Write a short note on IDE.
10. Explicate the features and tools in IDE.

Unit: 1 - Introduction to Principle of Programming Languages 41


DCA1209: Principle of Programming Languages

9. ANSWERS
Self-Assessment Questions
1. Write-only
2. Program performance
3. Cross-compiler
4. IDE
5. Enhancing developer productivity
6. Readability
7. Breaking down source code into tokens
8. True
9. False
10. False

Terminal Questions Answers


1. Refer to Section 2.1
2. Refer to section 2.2
3. Refer to section 2.3
4. Refer to section 3.2
5. Refer to section 3.3
6. Refer to section 3.3
7. Refer to sections 3.4 and 3.5
8. Refer to section 3.6
9. Refer to section 4.1
10. Refer to section 4.2

Unit: 1 - Introduction to Principle of Programming Languages 42


DCA1209: Principle of Programming Languages

10. REFERENCES
1. Maurizio Gabbrielli, Simone Martini, “Programming Languages: Principles and Paradigms”, 2010.
DOI 10.1007/978-1-84882-914-5
2. https://he.kendallhunt.com/sites/default/files/uploadedFiles/Kendall_Hunt/Content/Higher_E
ducation/Uploads/ChenTsai_ProgramLanguages_4e_Chapter1.pdf
3. Mike Grant, Zachary Palmer, Scott Smith, “Principles of Programming Languages”, 2024.
4. Software Engineering by Anuradha A. Puntambekar | PDF (scribd.com)

Unit: 1 - Introduction to Principle of Programming Languages 43


DCA1209: Principle of Programming Languages

BACHELOR OF COMPUTER APPLICATIONS


SEMESTER 2

DCA1209
PRINCIPLE OF PROGRAMMING
LANGUAGES
Unit: 2 - Language Spectrum and Study Motivation 1
DCA1209: Principle of Programming Languages

Unit – 2
Language Spectrum and Study
Motivation

Unit: 2 - Language Spectrum and Study Motivation 2


DCA1209: Principle of Programming Languages

TABLE OF CONTENTS

Fig No /
SL SAQ / Page
Topic Table /
No Activity No
Graph
1 Introduction - -
4-5
1.1 Objectives - -
2 Programming Paradigm - -
6 - 15
2.1 Different paradigms - -
3 Mixed-level languages - -

3.1 Comparison of mixed languages - -


16 - 22
3.2 Advantages of mixed-level Languages - -

3.3 Sample Programs - -


4 Motivation for studying programming languages - - 23 - 25
5 Summary - - 26 - 28
6 Self-Assessment Questions - 1 29 - 31
7 Glossary - - 32 - 33
8 Terminal Questions - - 34
9 Answers - - 35 - 36
10 References - - 37

Unit: 2 - Language Spectrum and Study Motivation 3


DCA1209: Principle of Programming Languages

1. INTRODUCTION
In the last unit 1, learners have explored the fundamentals of programming language design, including
the performance and features of different languages and their development over time. They studied
the structures of programming languages and the differences between compilation and
interpretation, including the stages of compilation and types of compilers and interpreters and also,
they learned about the comparison between compilers and interpreters. The unit also covered
programming environments, with a focus on Integrated Development Environments (IDEs) .

In this unit, learners will learn that the study of programming languages is an essential component of
computer science education, providing deep understanding of how different languages address
problems and how they can be efficiently employed in software development. This field includes
understanding various paradigms, studying mixed-level languages, and knowing the importance of
learning programming languages.

The field of programming languages covers a broad range of subjects that are essential to
understanding the variety and complexity of software development. It is essential to understand the
various programming paradigms among these. Programming paradigms are the foundational
methodologies or approaches to programming that determine how problems are resolved and how
code is organized. The programming paradigms encompass procedural, object-oriented, functional,
and logic programming, each providing distinct viewpoints and resources for addressing problems.
Procedural programming prioritizes a series of instructions, object-oriented programming structures
code around objects and their interactions, functional programming highlights the assessment of
mathematical functions without any side effects, and logic programming is founded on formal logic.

Mixed-level languages serve as an intermediary between low-level and high-level programming


languages. These languages give the same amount of flexibility and control as low-level languages,
such as direct memory access and manual memory management, while simultaneously providing the
same level of abstraction and convenience of use as high-level languages. This combination enables
developers to write code that is crucial for performance and directly interacts with hardware when
required. Also, it allows them to utilize high-level constructs to simplify development and enhance
code readability and maintainability. Some examples of mixed-level languages are C++, which has
both procedural and object-oriented features, and Rust, which guarantees memory safety without
relying on a garbage collector.

Unit: 2 - Language Spectrum and Study Motivation 4


DCA1209: Principle of Programming Languages

There are multiple reasons for studying programming languages. Learning several programming
languages can improve your proficiency in conveying complex ideas through code, as each language
provides distinct structures and abstractions. Having a thorough understanding of programming
languages facilitates the selection of the most suitable language for a specific task, hence enhancing
efficiency and effectiveness. Also, it enhances the learning of new languages by establishing a strong
base of essential programming concepts. Gaining an in-depth knowledge of the implementation
complexities of programming languages results in making more optimal design decisions, enhancing
program efficiency, and simplifying the process of identifying and fixing errors. Moreover, possessing
extensive expertise in programming languages enables you to optimize your utilization of the
languages you are currently familiar with by uncovering and implementing previously undiscovered
capabilities. Studying programming languages ultimately enhances the progress of computing by
facilitating improved assessment and acceptance of new languages.

To study this unit, learners should begin by participating in practical coding exercises in different
programming languages to gain an in-depth knowledge of diverse programming paradigms. Analyse
and differentiate the characteristics and applications of mixed-level languages by evaluating the
importance of acquiring expertise in numerous languages and consider how this information might
be utilized in your programming activities. Improve your understanding by actively participating in
both theoretical topics and practical challenges.

1.1. Objectives
After studying this unit, you should be able to:

• Define different programming paradigms.


• Explain the differences between various
programming paradigms.
• Describe the benefits and challenges of mixed-
level languages.
• Explain the importance of Programming
languages.

Unit: 2 - Language Spectrum and Study Motivation 5


DCA1209: Principle of Programming Languages

2. PROGRAMMING PARADIGM
Programming paradigms include several approaches or frameworks for organizing programs or
programming languages. Each paradigm incorporates unique frameworks, attributes, and concepts
for addressing common programming issues. The existence of different programming paradigms is
similar to the existence of multiple programming languages. Various paradigms are more suitable for
different problem kinds, hence it is logical to use different paradigms for distinct program types.

Also, the methodologies that describe each paradigm have changed over time. Innovations in both
software and technology have brought up new approaches that were previously unavailable.

Programming paradigms are conceptual structures rather than physical languages or tools. They
represent a collection of concepts and procedures that have been widely accepted, followed, and
developed by the programming community.

Programming languages sometimes do not exactly adhere to a single paradigm. Certain programming
languages are intentionally created to align with a specific paradigm, containing characteristics that
facilitate that particular style of programming.

But there exist "multi-paradigm" languages that enable developers to write code following to several
paradigms (JavaScript and Python are prominent examples).

Also, programming paradigms are not mutually exclusive; it is feasible to include methodologies from
many paradigms in the same project without experiencing any problems.

2.1. Different paradigms


The popular different Programming Paradigms are:
1. Imperative Programming
2. Procedural Programming
3. Functional Programming
4. Declarative Programming
5. Object-Oriented Programming

Unit: 2 - Language Spectrum and Study Motivation 6


DCA1209: Principle of Programming Languages

Imperative Procedural Functional


Programming Programming Programming

Object-
Declarative
Oriented
Programming
Programming

i. Imperative Programming
Imperative programming involves providing the computer with specific and sequential
instructions to execute. The term "imperative" is used because as programmers, we clearly specify
the actions that the computer must perform in a highly particular manner.

Imperative programming is concerned with specifying the sequential operations of a program in


a step-by-step manner.

Example: To calculate the sum of numbers from 1 to 10.


#include <stdio.h>
int main ( ) {
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += i;
}
printf("Sum: %d\n", sum);
return 0;
}

Here, the program is instructed to initialize the variable "sum" to 0 and then iteratively add the
numbers from 1 to 10 to the variable "sum".

Observe that the instructions are carried out in a specific sequence, and each step alters the
program's state.

Unit: 2 - Language Spectrum and Study Motivation 7


DCA1209: Principle of Programming Languages

ii. Procedural Programming


Procedural programming is a programming paradigm that is based on structured programming.
The program is structured using procedure/function calls, which include dividing it into
procedures, sometimes referred to as functions or subroutines. These processes consist of a
sequence of computing steps to be executed. Procedural programming focuses on the order of
executable statements and the utilization of procedures to organize the code, therefore improving
its comprehensibility, debugging capabilities, and maintainability.

Example: To calculate a factorial of a number.


#include <stdio.h>
// Function to calculate factorial
int factorial(int n) {
if (n == 0 || n==1) {
return 1;
} else {
return n * factorial(n - 1);
}
}
int main () {
int num;
printf ("Enter a number: ");
scanf ("%d", &num);
// Call the factorial function
int result = factorial(num);
// Print the result
Printf ("Factorial of %d is %d\n", num, result);
return 0;
}

Unit: 2 - Language Spectrum and Study Motivation 8


DCA1209: Principle of Programming Languages

The programme is instructed to define a function named "factorial" that takes an integer "n" as its
parameter. The function applies recursion to get the factorial of n. The algorithm includes a base
case where it returns 1 if n is equal to 0 or 1. On the other hand, it yields the result of multiplying
n by the factorial of n - 1. Within the main function, we use a prompt that requests the user's input
of a numerical value. The value is then read and subsequently assigned to the variable num.
Subsequently, we call upon the factorial function, using the variable num as the input parameter,
and subsequently assign the resulting output to the variable result. Finally, we display the result
on the console, displaying the factorial of the provided integer.

iii. Functional Programming


Functional programming is a programming paradigm that involves building programs by applying
and combining functions. The focus is on immutability, pure functions (functions without side
effects), and higher-order functions (functions that can accept or return them as results).

While C is mainly an imperative language, it is still possible to incorporate certain functional


programming concepts within it, like considering functions as first-class citizens.

Example: To calculate a factorial of a number.


#include <stdio.h>
// Define a recursive function to calculate factorial
int factorial(int n) {
if (n == 0) {
return 1; // Base case: factorial of 0 is 1
} else {
return n * factorial (n - 1); // Recursive case
}
}
int main() {
int number = 5; // Example input

Unit: 2 - Language Spectrum and Study Motivation 9


DCA1209: Principle of Programming Languages

int result = factorial(number); // Call the recursive function


printf ("Factorial of %d is %d\n", number, result); // Output the result
return 0;
}
Output:

The above C program computes the factorial of a number by using a recursive function,
demonstrating the concept of functional programming in an imperative language. The factorial
function is described as a recursive function that decrements the input number by one until it hits
the base case of zero, at which point it returns one. This solution uses the notion of recursion,
which is a fundamental element of functional programming, to solve the problem. The number 5
is used as an example input in the main function. The factorial function is invoked with this
number, and the resulting value, 120, is displayed. The recursion operates by multiplying the
current number n with the factorial of n-1, progressively accumulating the result through a series
of recursive operations until the base case is achieved. This showcases the application of
functional approaches in languages such as C. The program successfully computes the factorial of
5 as 120, and this is the output.

iv. Declarative Programming


Declarative programming is a programming paradigm designed to simplify interactions with
computers by focusing on what to achieve rather than detailing how to achieve it. It abstracts the
underlying complexity of execution, enabling developers to specify the desired outcome without
worrying about the step-by-step instructions. This makes declarative programming closer to
human reasoning and natural language, enhancing its intuitiveness and efficiency. Unlike
imperative programming, which relies on modifying states and specifying control flows,
declarative programming emphasizes immutability and side-effect-free computations. Examples
include SQL, where queries specify what data to retrieve without detailing how, and HTML/CSS,
which define structure and style without describing rendering processes. Declarative
programming promotes cleaner, more predictable code and is easier to debug and maintain due

Unit: 2 - Language Spectrum and Study Motivation 10


DCA1209: Principle of Programming Languages

to its focus on logic rather than implementation. By separating the what from the how, it allows
developers to focus on problem-solving while frameworks or interpreters handle the operational
details, making it a preferred approach for modern software development. Its advantages, such as
improved readability, scalability, and reduced cognitive load, make it highly applicable across
functional programming, logic programming, and markup languages.

Example: Summing Even numbers


#include <stdio.h>
#include <stdlib.h>

// Function to check if a number is even


int isEven(int num) {
return num % 2 == 0;
}

// Higher-order function to apply an operation on an array and reduce to a single value


int reduce(int* nums, int size, int (*predicate)(int), int initial, int (*operation)(int, int)) {
int result = initial;
for (int i = 0; i < size; i++) {
if (predicate(nums[i])) {
result = operation(result, nums[i]);
}
}
return result;
}

// Operation to sum two numbers


int add(int a, int b) {
return a + b;
}

int main() {
int nums[] = {1, 4, 3, 6, 7, 8, 9, 2};

Unit: 2 - Language Spectrum and Study Motivation 11


DCA1209: Principle of Programming Languages

int size = sizeof(nums) / sizeof(nums[0]);

// Use reduce function to calculate the sum of even numbers


int sum = reduce(nums, size, isEven, 0, add);

// Print the result


printf("Sum of even numbers: %d\n", sum);

return 0;
}

Output:

In this example, we want to use a more declarative method in C to add up only the even numbers
in an array. We create a method called isEven that checks if a number is even. The primary logic
is contained in the sumIf function, which hides the process of iterating and checking conditions.
You can pass an array, its size, and a predicate function to this function. As it goes through the list,
it applies the predicate to each item and adds up the ones that meet the condition. We set up an
array of integers in the main method and then use this array and the isEven predicate to call sumIf.
Finally, we print the sum of all the even numbers. This method hides the details of how the sum is
figured and instead focuses on what needs to be done, which is in line with the ideas of declarative
programming. The program's result, "Sum of even numbers: 20," shows that only the even
numbers in the array were added up, which is what was meant by "declarative style."

v. Object-Oriented Programming
The fundamental principle of Object-Oriented Programming (OOP) is to partition responsibilities
into discrete entities that are implemented as objects. An entity will consist of a certain collection
of properties and methods that are related to it.

Unit: 2 - Language Spectrum and Study Motivation 12


DCA1209: Principle of Programming Languages

Object-oriented programming (OOP) extensively uses classes, which serve as templates or


prototypes for producing new objects based on the specifications supplied by the programmer.
Instances refer to objects that are instantiated from a class.

Essential Principles of Object-Oriented Programming


• Objects are physical representations of abstract classes. These entities incorporate both data
(properties) and behaviors (methods).
An example of the Dog class that possesses attributes/properties such as name and age, as well
as behaviors/methods such as bark.
• Classes are templates/blueprints used to create objects. The class defines the properties and
methods that will be possessed by the objects created from it.
An example: A Dog class that defines what properties a dog will have and what actions it can
perform.
• Properties refer to variables that store data associated with an object.
Example: The name and age of a Dog
• Methods are a set of functions that specify the actions that an object is capable of performing.
An implementation of a bark method for a Dog class that enables the dog to produce a bark
sound.
• Instances refer to the particular objects that are generated from a class. Every occurrence
possesses its unique collection of properties and is capable of executing its distinct set of
operations.
Example: Creating a specific dog named Buddy from the Dog class.

Example: C++ Program to demonstrate ‘Dog’ class.


#include <iostream>
#include <string>
class Dog {
public:
// Constructor
Dog(std::string name, int age) : name(name), age(age) {}
// Method to simulate barking
void bark() {
std::cout << name << " is barking" << std::endl;
}

Unit: 2 - Language Spectrum and Study Motivation 13


DCA1209: Principle of Programming Languages

private:
std::string name; // Property to store the name of the dog
int age; // Property to store the age of the dog
};
int main() {
// Creating an instance of Dog
Dog myDog("Buddy", 3);
// Calling the bark method
myDog.bark();
return 0;
}
Output:

The above C++ program defines a Dog class that contains properties and methods, demonstrating
the fundamentals of Object-Oriented Programming (OOP). The Dog class contains two private
instance variables, name, and age, which are used to hold the name and age of the dog, respectively.
When a Dog object is created, a public constructor is available to initialize these properties. Also,
there exists a publicly accessible method called "bark" that generates an output message
indicating that the dog is currently emitting a barking sound. In the main function, an instance of
the Dog class named myDog is created with the name "Buddy" and age 3. Next, we invoke the bark
method on the myDog object, resulting in the output "Buddy is barking" being displayed on the
console. This program demonstrates the fundamental OOP principles of encapsulation, which
involves organizing relevant attributes and methods within the Dog class. It also showcases the
utilization of constructors and methods to instantiate and manipulate objects.

Unit: 2 - Language Spectrum and Study Motivation 14


DCA1209: Principle of Programming Languages

Examples of Programming Paradigms


Imperative Procedural Functional Declarative Object-Oriented
Programming Programming Programming Programming Programming

Assembly C Haskell SQL Java


Fortran Pascal Lisp HTML C++
Algol Ada ML Prolog Python
BASIC Fortran Scala XQuery Ruby
(procedural
subset)
COBOL Modula-2 Erlang CSS C#

Unit: 2 - Language Spectrum and Study Motivation 15


DCA1209: Principle of Programming Languages

3. MIXED-LEVEL LANGUAGES
Mixed-level languages, or hybrid languages, are computer languages that combine characteristics
from both low-level and high-level programming paradigms. These languages enable the combination
of low-level language features, such as direct memory access and manual memory management, with
high-level language features, such as object-oriented and functional programming constructs,
providing both flexibility and control as well as abstraction and ease of use. Employing this hybrid
methodology can result in software that is both more efficient and resilient, as developers can fine-
tune critical sections of the code for optimal performance while utilizing higher-level structures to
handle intricate aspects.

Characteristics of Mixed-Level Languages


• Flexibility: Mixed-level languages offer the capacity to transition between low-level and high-
level programming constructs depending on the specific needs of the work.
Example: In the programming language C++, developers can utilise low-level pointers and
manual memory management in addition to high-level object-oriented capabilities such as
classes and inheritance.
• Performance optimisation is achieved by providing developers with the ability to write
highly optimised code for critical components of their programmes through the use of mixed-
level languages, which allow low-level control.
Example: Rust offers fine-grained control over memory and performance, while
simultaneously guaranteeing safety through its ownership system.
• Abstraction: The utilisation of high-level features in mixed-level languages enables the reuse
of code, enhances modularity, and improves readability.
Example: Swift is a programming language that combines a user-friendly syntax with efficient
performance, making it well-suited for developing applications and programming systems.
• Safety: Numerous mixed-level languages incorporate safety mechanisms to prevent common
programming problems, such as memory leaks and buffer overflows.
Example: Rust's borrow checker guarantees memory safety without requiring a garbage
collector.

Examples of Mixed-Level Languages


i. C++: Commonly used in the fields of game development, systems programming, and
applications that require high performance.

Unit: 2 - Language Spectrum and Study Motivation 16


DCA1209: Principle of Programming Languages

ii. Rust: Used in the fields of systems programming, web assembly, and network services.
iii. Swift: Primarily used for iOS and macOS development, as well as various other applications.
iv. Objective-C: Enables programming at both low and high levels within a single language.

3.1. Comparison of Mixed-Level Languages:


Criteria C++ Rust Swift Objective-C

Combines low-
Design Ensures safety Safe, fast, Extends C with
level features
Philosophy and concurrency expressive OOP
with OOP

Ownership Automatic Manual memory


Memory Manual memory
system prevents reference management +
Management management
memory issues counting (ARC) ARC

Fearless
Thread-based Thread-based Thread-based
Concurrency concurrency
concurrency concurrency concurrency
model

Prone to memory Safe by design, Prone to memory


Guarantees
Safety safety issues fewer runtime safety issues
memory safety
(manual) crashes (manual)

High, optimized High, especially


High, with fine- High, with safety
Performance for Apple for iOS/macOS
grained control guarantees
hardware apps

Verbose, C-like
Complex and Modern and Clean and
Syntax with OOP
verbose concise readable
extensions

Systems, game Systems iOS and macOS iOS and macOS


Use Cases development, programming, application legacy application
high-performance web assembly development development

Unit: 2 - Language Spectrum and Study Motivation 17


DCA1209: Principle of Programming Languages

Growing, with Rich ecosystem


Ecosystem and An extensive, Extensive, mature
strong tooling for Apple
Libraries mature ecosystem ecosystem
support platforms

Various compilers
Compiler rustc Swift compiler Clang
(GCC, Clang)

Interoperable
Interoperable Interoperable Interoperable
Interoperability with Objective-C
with C with C with C
and C

3.2. Advantages of Mixed-level Languages


• Mixed-level languages enable developers to utilize both low-level and high-level programming
structures, granting them the flexibility to select the most suitable method for various
components of the application.
• Performance optimization can be achieved by using mixed-level languages, which allow direct
access to hardware and memory management, enabling the optimization of code sections that
are essential to performance.
• Safety and reliability are prioritized in many mixed-level languages, which include features
designed to prevent common programming problems such as buffer overflows and null
pointer dereferencing.
• Abstraction and Modularity: These languages provide advanced concepts such as classes,
generics, and modules, which facilitate the reuse of code, promote the organization of code
into separate modules, and enhance the ease of maintaining the code.
• Interoperability: Languages with different levels of complexity typically include features that
allow them to work together with other languages, hence increasing their flexibility and
adaptability.
• Versatility: Mixed-level languages can be employed in a wide range of applications, spanning
from low-level system programming to high-level application development.

Challenges of Mixed-level Languages:


• Complexity: The requirement to understand both low-level and high-level programming
notions may make mixed-level languages more demanding to acquire and succeed with.

Unit: 2 - Language Spectrum and Study Motivation 18


DCA1209: Principle of Programming Languages

• Manual resource management, including manual memory management and low-level control,
can result in mistakes such as memory leaks, buffer overflows, and dangling pointers if not
properly managed.
• Comparison between Safety and Performance Considerations: Achieving both memory and
thread safety while retaining optimal performance can be a difficult task that might require
making compromises.
• Debugging and maintenance: Debugging low-level code may present greater challenges and
consume more time in comparison to high-level code, particularly when addressing memory-
related problems.
• Tooling and Ecosystem: While many mixed-level languages include robust tooling support,
others may have less developed ecosystems, which can impact productivity.
• Concurrency Issues: Effectively managing concurrency can be complex, necessitating
developers to possess a deep understanding of concurrency models and synchronization
techniques.

3.3. Sample Programs


Finding the sum of an array of integers.
C++ Program:
#include <iostream>
#include <vector>

// Mixed-Level: Using low-level arrays with high-level vector operations


int sum(const std::vector<int>& arr) {
int total = 0;
for (int num : arr) {
total += num;
}
return total;
}

int main() {
std::vector<int> arr = {1, 2, 3, 4, 5};
int result = sum(arr);

Unit: 2 - Language Spectrum and Study Motivation 19


DCA1209: Principle of Programming Languages

std::cout << "Sum of the array: " << result << std::endl;
return 0;
}
Output:

The given C++ program demonstrates calculating the sum of an array of integers using a combination
of low-level and high-level operations, showcasing the mixed-level language capabilities of C++. The
program begins with including the necessary header <iostream> for input-output operations and
<vector> for using the std::vector class (The std::vector is a part of the C++ Standard Library and is
defined in the <vector> header. It is a dynamic array that can change size, allowing elements to be
added or removed at runtime. The std::vector class provides several functionalities that make it easier
to manage arrays, including automatic memory management, bounds checking, and various member
functions to manipulate the elements). A function sum is defined, which takes a constant reference
to a std::vector<int> and returns the total sum of its elements. Inside the function, a local variable
total is initialized to 0, and a range-based for loop iterates over each element of the vector, adding
each element's value to total. The final sum is then returned. In the main function, a std::vector<int>
named arr is initialized with the values 1, 2, 3, 4, and 5. The sum function is called with arr as an
argument, and the result is stored in the variable result. Finally, the result is printed to the console
using std::cout. This program effectively uses C++'s high-level vector operations for dynamic array
handling while employing low-level iteration and sum calculation to achieve the desired functionality.

Rust Program:
// Mixed-Level: Using Rust's high-level Vec with manual iteration for sum calculation
fn sum(arr: &Vec<i32>) -> i32 {
let mut total = 0;
for &num in arr.iter() {
total += num;
}
total

Unit: 2 - Language Spectrum and Study Motivation 20


DCA1209: Principle of Programming Languages

fn main() {
let arr = vec![1, 2, 3, 4, 5];
let result = sum(&arr);
println!("Sum of the array: {}", result);
}

Output:

The above program uses Rust's high-level Vec type for dynamic arrays and manual iteration to
calculate the sum.

Function Definition:
• fn sum(arr: &Vec<i32>) -> i32 {
• Defines a function named sum that takes a reference to a Vec<i32> (vector of 32-bit integers)
as an argument and returns an i32 (32-bit integer).
• fn is the keyword for defining functions.
• arr: &Vec<i32> specifies that arr is a reference to a vector of 32-bit integers, meaning the
vector itself is not copied or moved, ensuring efficient access.

Variable Declaration:
• let mut total = 0;
• Declares a mutable variable total and initializes it to 0.
• let is the keyword for variable declaration.
• mut indicates that the variable is mutable and can be changed.

For Loop:
• for &num in arr.iter() {
• Iterates over each element in the vector arr.
• for is the keyword for loop iteration.
• &num destructures each element, obtaining a reference to the value.
• arr.iter() returns an iterator over the elements of arr.

Unit: 2 - Language Spectrum and Study Motivation 21


DCA1209: Principle of Programming Languages

Update Total:
• total += num;
• Adds the value of num to total for each iteration.
• += is the operator for addition and assignment, updating total with the new sum.

Return Statement:
• total
• Returns the final value of total after the loop completes.
• In Rust, the last expression in a function is implicitly returned if it is not followed by a
semicolon.

Main Function:
• fn main() {
• Defines the main function, which is the entry point of the Rust program.

Vector Initialization:
• let arr = vec![1, 2, 3, 4, 5];
• Creates a vector arr with the elements 1, 2, 3, 4, and 5.
• vec! is a macro that creates a new Vec<i32> initialized with the specified elements.

Function Call:
• let result = sum(&arr);
• Calls the sum function, passing a reference to the vector arr.
• The result of the function call is stored in the variable result.

Print Result:
• println!("Sum of the array: {}", result);
• Prints the result to the console.
• println! is a macro for printing to the standard output.
• "Sum of the array: {}" is a format string where {} is replaced by the value of result.

Unit: 2 - Language Spectrum and Study Motivation 22


DCA1209: Principle of Programming Languages

4. MOTIVATION FOR STUDYING PROGRAMMING


LANGUAGES
The primary purpose of studying the principles of programming languages is to gain a thorough
understanding of the fundamental concepts involved in designing, implementing, and applying
programming languages.

The design and implementation of computer languages are frequently considered subjective, missing
defined organizing principles and universally recognized truths. Research laboratories and
enterprises employ numerous languages daily, each with its supporters and critics. Some argue that
all programming languages are essentially equal, with the only difference being a question of
individual preference. Can we truly assert that the computer programming language Java is superior
(or inferior) to C++? Is Scheme superior to Lisp? Is machine-level language superior to either of them?
The superiority of programming languages and the reasons behind it remain a subject of inquiry. The
study of the principles of programming languages facilitates an improved understanding of the
fundamental logic of programming languages, as well as their supporters and critics. It also facilitates
understanding of programming language paradigms.

i. Enhanced Expression of Ideas


• Limited Communication in Natural Languages: Just as a small grasp of natural languages
restricts your ability to communicate effectively, having a limited understanding of
programming languages and constructs can hinder your problem-solving capabilities.
• Broadening Your Toolkit: By learning multiple programming languages, you can apply
concepts from one language to another, even if the second language doesn’t natively support
those constructs. This broadened perspective enhances your ability to tackle diverse
programming challenges.

ii. Better-Informed Language Selection


• Avoiding One-Size-Fits-All: If you only know one programming language, you are likely to use
it for all tasks, even when it’s not the best fit for the job. This can lead to inefficient and
suboptimal solutions.
• Choosing the Right Tool: Familiarity with several languages enables you to select the most
appropriate one for each specific task. This can improve your efficiency and effectiveness as a
programmer.

Unit: 2 - Language Spectrum and Study Motivation 23


DCA1209: Principle of Programming Languages

• Willingness to Learn New Languages: Knowing that another language is better suited to a
particular problem can motivate you to learn it, thereby expanding your skill set and making
you more adaptable.

iii. Accelerated Learning of New Languages


• Building on Fundamental Concepts: Understanding the core principles of programming makes
it easier to learn new languages. For instance, knowledge of object-oriented concepts in C++
can facilitate the learning of Java.
• Language Learning Synergy: Just as knowing multiple natural languages can make it easier to
learn additional ones, familiarity with several programming languages can speed up the
acquisition of new ones. This synergy makes you a more versatile and agile programmer.

iv. Deeper Insight into Language Implementation


• Understanding Design Choices: Knowing how programming languages are implemented leads
to a better understanding of their design and the rationale behind various constructs.
• Appreciating Trade-offs: This knowledge helps you appreciate the trade-offs involved in
language design and how different constructs affect performance and usability.
• Improving Efficiency and Debugging: A deeper understanding of implementation can enhance
your ability to write efficient code and debug issues effectively, leading to more robust and
performant software.

v. More Effective Use of Known Languages


• Expanding Your Knowledge: Programming languages are large and complex, and you may only
use a subset of their features. Studying them in more depth can reveal useful features that you
were previously unaware of.
• Leveraging New Features: Learning about previously unknown and unused features can help
you make better use of the languages you already know, improving your productivity and the
quality of your code.

vi. Advancing the Field of Computing


• Adoption of Better Languages: Historically, some better programming languages were not
widely adopted because they were not well understood. Studying programming languages
helps ensure that new, potentially superior languages are evaluated and adopted based on
their merits.

Unit: 2 - Language Spectrum and Study Motivation 24


DCA1209: Principle of Programming Languages

• Informed Language Evaluation: By understanding programming languages in general, you


become a better judge of new languages and their potential impact on the field. This helps drive
the overall advancement of computing by promoting the adoption of innovative and effective
programming paradigms.

Unit: 2 - Language Spectrum and Study Motivation 25


DCA1209: Principle of Programming Languages

5. SUMMARY
In this unit, learners have studied that Studying programming languages is an essential part of
computer science education, as it is necessary for understanding the many approaches and tools used
in software development. This thorough review includes various programming paradigms, mixed-
level languages, and the reason for studying programming languages. These elements collectively
expand a programmer's perspective, enhance problem-solving abilities, and optimise programming
practices in terms of efficiency and effectiveness.

Programming paradigms are foundational methodologies in programming that determine the


structure of programmes and the approach to problem-solving. The primary paradigms encompass
imperative, procedural, object-oriented, functional, and declarative programming. Imperative
programming is centred around executing tasks by using a series of commands that alter the state of
a programme. This paradigm involves the direct manipulation of memory and the alteration of state,
making it well-suited for activities that necessitate explicit control over state changes. Procedural
programming, which is a subset of imperative programming, organises programmes into procedures
or functions, encouraging a modular approach that allows for the reuse of code blocks. This paradigm
is advantageous for activities that can be fragmented into smaller, controllable sub-tasks. Object-
oriented programming (OOP) is a software design approach that focuses on organising data (objects)
and the methods that manipulate these objects. It highlights the concepts of encapsulation,
inheritance, and polymorphism. Object-oriented programming (OOP) is well-suited for complex
structures that necessitate modularity and the ability to reuse components. Functional programming
is an approach that views processing as the process of evaluating mathematical functions. It
emphasises avoiding the alteration of state and changeable data. It prioritises immutability, higher-
order functions, and pure functions, which makes it well-suited for applications that involve intricate
data manipulations and concurrency. Declarative programming is a programming paradigm that
emphasises expressing the desired outcome of a computation rather than specifying the step-by-step
instructions for achieving it. It focuses on the logic of computation rather than the control flow. This
paradigm is highly efficient for operations such as database searches and configuration management,
where the desired result takes precedence over the specific steps involved.

Mixed languages integrate characteristics from both low-level and high-level programming
paradigms. These languages provide the versatility and command that is commonly seen in low-level
languages, such as direct access to memory and manual management of memory, while also offering

Unit: 2 - Language Spectrum and Study Motivation 26


DCA1209: Principle of Programming Languages

the abstraction and user-friendliness that is characteristic of high-level languages. By employing this
hybrid method, developers can enhance the efficiency of code that is essential to performance, while
still ensuring that it remains easy to understand and maintain. Mixed-level languages have the
capability to alternate between low-level and high-level elements, allowing for precise manipulation
of hardware and memory to optimise speed, while utilising high-level elements to enhance code
organisation and readability. In addition, they integrate safety measures to mitigate prevalent
programming errors. Some examples of mixed-level languages are C++, Rust, and Swift. C++
integrates low-level functionalities such as pointers and manual memory management with high-
level object-oriented programming constructs, making it extensively used for game development,
systems programming, and applications that demand superior performance. Rust offers precise
management of memory and concurrency, guaranteeing safety through its ownership structure. This
makes it well-suited for systems programming and applications that demand exceptional
dependability and performance. Swift is a programming language that prioritises safety and
performance. It has a user-friendly syntax and is optimised for developing programmes on iOS and
macOS. It is widely used for creating applications for Apple platforms. Multilevel languages provide
flexibility that is appropriate for a diverse array of applications, allowing programmers to create
highly efficient code for areas that require optimal speed, while also ensuring safety and minimising
runtime errors. Nevertheless, they also provide difficulties, necessitating understanding of both
fundamental and advanced programming principles, which might be complex. Manual resource
management in these programming languages is susceptible to errors if not carefully managed, such
as memory leaks and buffer overflows.

Learning programming languages offers numerous of advantages that improve a programmer's skills
and contribute to the progress of computing. Having a comprehensive understanding of programming
languages and constructs improves problem-solving abilities by increasing the range of terms and
concepts that a programmer can utilize. Principles acquired in one language can be utilised in other
languages, even if those languages do not inherently accommodate those structures. Proficiency in
various languages empowers programmers to select the most appropriate language for a given
assignment, hence enhancing their decision-making abilities and adaptability. Knowing many
languages can serve as a source of inspiration to acquire proficiency in a new language, especially if
it is more suitable for addressing a specific situation. Gaining a strong foundation of fundamental
programming ideas simplifies the process of acquiring new programming languages, as having a prior
understanding of many languages may help in the learning of more languages. Acquiring knowledge

Unit: 2 - Language Spectrum and Study Motivation 27


DCA1209: Principle of Programming Languages

about the implementation of languages enhances understanding of their design and the reasoning
behind their structures, facilitating the creation of efficient code and the successful resolution of
debugging problems. Acquiring knowledge about the fundamental concepts of programming
languages is essential for developing proficiency and effectiveness as a programmer.

Unit: 2 - Language Spectrum and Study Motivation 28


DCA1209: Principle of Programming Languages

6. SELF-ASSESSMENT QUESTIONS
Multiple choice Questions
1 Which paradigm focuses on sequences of commands to change a program's state?
A) Functional Programming
B) Object-Oriented Programming
C) Imperative Programming
D) Declarative Programming
2 Which programming paradigm treats computation as the evaluation of mathematical
functions and avoids changing state?
A) Procedural Programming
B) Imperative Programming
C) Object-Oriented Programming
D) Functional Programming
3 Which of the following is an example of a mixed-level language?
A) Java
B) Rust
C) Python
D) SQL
4 In object-oriented programming, which concept allows objects to inherit properties and
methods from other objects?
A) Encapsulation
B) Polymorphism
C) Inheritance
D) Abstraction
5 Which paradigm is best suited for database queries?
A) Imperative Programming
B) Functional Programming
C) Procedural Programming
D) Declarative Programming
6 Which language is primarily used for iOS and macOS development?
A) Java

Unit: 2 - Language Spectrum and Study Motivation 29


DCA1209: Principle of Programming Languages

B) Swift
C) C++
D) Rust
7 Which feature of mixed-level languages helps prevent common programming errors like
buffer overflows?
A) Performance optimization
B) Flexibility
C) Safety features
D) Abstraction
8 Which of the following languages is an example of an imperative language?
A) Haskell
B) Prolog
C) C
D) Lisp
9 What type of errors do mixed-level languages aim to prevent through safety features?
A) Syntax errors
B) Runtime errors
C) Memory safety errors
D) Logic errors
Fill in the Blanks:

10 The programming paradigm that focuses on what to achieve rather than how to achieve it is
called _____________________.
11 __________ ensures memory safety without using a garbage collector through its ownership
system.
12 ____________ avoids changing state and mutable data.

True or False:

13 C++ is an example of a mixed-level language.

14 Declarative programming involves explicit control flow management.

Match the Following:

Unit: 2 - Language Spectrum and Study Motivation 30


DCA1209: Principle of Programming Languages

15 Column A Column B

Imperative Programming SQL

Object-Oriented Programming Rust

Functional Programming Java

Declarative Programming C

Mixed-Level Language Haskell

Unit: 2 - Language Spectrum and Study Motivation 31


DCA1209: Principle of Programming Languages

7. GLOSSARY
Financial Management is concerned with the procurement of the least cost funds, and its effective

Imperative It is a programming paradigm that includes statements to modify the


-
Programming state of a programme.

Procedural It is a type of imperative programming that organises programmes into


-
programming procedures or functions.

Object-Oriented
It is a programming paradigm that revolves around the idea of "objects"
Programming -
that consist of both data and methods.
(OOP)

Functional It is a programming paradigm that involves constructing programmes by


-
Programming applying and combining functions.

Declarative It is a programming approach that focuses on expressing the logic of


-
Programming computation without explicitly specifying its control flow.

Mixed-Level Programming languages that integrate low-level and high-level


-
Languages characteristics, offering both control and abstraction.

It is a computer language that prioritises safety, particularly in terms of


Rust -
concurrency.

It is a flexible programming language created by Apple specifically for


Swift - developing applications for iOS and macOS platforms. It is compiled and
supports multiple programming paradigms.
It refers to the process of combining data with the corresponding
Encapsulation - methods that manipulate that data. It also involves limiting direct access
to certain components of an object.
It is a fundamental concept in Object-Oriented Programming (OOP) that
Inheritance - allows one class to acquire the characteristics and behaviours of another
class.

It refers to the capacity of distinct objects to exhibit varied behaviours


Polymorphism -
when the same function or method is invoked.

Unit: 2 - Language Spectrum and Study Motivation 32


DCA1209: Principle of Programming Languages

A function in which the output is solely defined by the input values and
Pure Function -
does not produce any significant side effects.

It refers to data that is unable to be altered or modified after it has been


Immutable data -
initially created.

Unit: 2 - Language Spectrum and Study Motivation 33


DCA1209: Principle of Programming Languages

8. TERMINAL QUESTIONS
1. Define Programming Paradigms and mention the different paradigms.
2. Elucidate Imperative programming with an example.
3. Explain procedural Programming with an example.
4. Write a program to compute the factorial of a number using functional programming and explain
the same.
5. Write a program to perform the addition of even numbers using declarative programming.
6. Explicate the essential principles of Object-oriented Programming.
7. Explain the characteristics of Mixed-level languages.
8. Compare and contrast Mixed-level languages.
9. Write a program to perform a sum operation on array elements using Rust language.
10. Write a short note on the importance of studying programming languages.

Unit: 2 - Language Spectrum and Study Motivation 34


DCA1209: Principle of Programming Languages

9. ANSWERS
Self-Assessment Questions
1. Imperative Programming
2. Functional Programming
3. Rust
4. Inheritance
5. Declarative Programming
6. Swift
7. Safety features
8. C
9. Memory safety errors
10. Declarative programming
11. Rust
12. Functional Programming
13. True
14. False
15. Imperative Programming - C
Object-Oriented Programming - Java
Functional Programming - Haskell
Declarative Programming - SQL
Mixed-Level Language – Rust

Terminal Questions Answers


1. Refer to sections 2 and 2.1
2. Refer to section 2.1
3. Refer to section 2.1
4. Refer to section 2.1
5. Refer to section 2.1
6. Refer to section 2.1
7. Refer to section 3
8. Refer to section 3.1
9. Refer to section 3.3

Unit: 2 - Language Spectrum and Study Motivation 35


DCA1209: Principle of Programming Languages

10. Refer to section 4

Unit: 2 - Language Spectrum and Study Motivation 36


DCA1209: Principle of Programming Languages

10. REFERENCES
1. Robert W. Sebesta, “Concepts of Programming Languages”, 2012, Tenth Edition, . ISBN 10: 0-13-
139531-9.
2. Programming Paradigms – Paradigm Examples for Beginners (freecodecamp.org)
3. Programming Paradigms PDF | PDF | Programming Paradigms | Functional Programming
(scribd.com)

Unit: 2 - Language Spectrum and Study Motivation 37


DCA1209: Principle of Programming Languages

BACHELOR OF COMPUTER APPLICATIONS


SEMESTER 2

DCA1209
PRINCIPLE OF PROGRAMMING
LANGUAGES
Unit: 3 - Names, Scope, and Bindings 1
DCA1209: Principle of Programming Languages

Unit – 3
Names, Scope, and Bindings

Unit: 3 - Names, Scope, and Bindings 2


DCA1209: Principle of Programming Languages

TABLE OF CONTENTS

Fig No /
SL SAQ / Page
Topic Table /
No Activity No
Graph
1 Introduction - -
4-5
1.1 Objectives - -
2 Names - - 6-9
3 Scopes - -

3.1 Static Scoping - - 10 - 16

3.2 Dynamic Scope - -


4 Concept of Binding Time - -

4.1 Binding of Attributes to Variables - - 17 - 21

4.2 Type Bindings - -


5 Object Lifetime and Storage Management - -

5.1 Static Allocation 1 -


22 - 27
5.2 Stack-Based Allocation 2 -

5.3 Heap-Based Allocation 3 -


6 Summary - - 28
7 Self-Assessment Questions - 1 29 - 30
8 Glossary - - 31
9 Terminal Questions - - 32
10 Answers - - 33
11 References - - 34

Unit: 3 - Names, Scope, and Bindings 3


DCA1209: Principle of Programming Languages

1. INTRODUCTION
In the previous unit, learners studied various programming paradigms, exploring the fundamental
concepts and examples of imperative, procedural, functional, declarative, and object-oriented
programming. They delved into mixed-level languages, understanding how they combine low-level
and high-level programming features. Learners compared different mixed-level languages, examined
their advantages, such as flexibility, performance optimisation, and safety, and reviewed sample
programs to see these concepts in action.

In this unit, learners will study the high-level programming languages and derive their name from the
high degree of abstraction they provide compared to the assembly languages they replaced. This
abstraction separates language features from the details of specific computer architectures,
promoting machine independence and ease of programming. The early languages like Fortran, Algol,
and Lisp were designed with these goals in mind, allowing programs to run on various machines and
simplifying them for human to understand.

Machine independence means that a programming language should not depend on the features of any
particular machine's instruction set for efficient implementation. Although machine dependencies
can still pose problems, the push for machine independence has largely shaped language design over
the past decades. In contrast, ease of programming is a more elusive goal, influencing every aspect of
language design and evolving through a mix of aesthetics and trial and error.

Names, scope, and bindings are fundamental concepts in language design. A name is a symbolic
identifier representing variables, constants, operations, types, and other elements. Names simplify
programming by allowing references to higher-level concepts rather than low-level details like
memory addresses. This abstraction reduces complexity, enabling programmers to focus on the
purpose and function of code rather than its implementation details. For instance, subroutines and
classes provide control and data abstractions, respectively, by hiding complex code and data
representation behind simple interfaces.

Key names-related issues include binding time, storage allocation, scope rules, and referencing
environments. Binding time refers to when a name is bound to the entity it represents, impacting
various design decisions in language implementation. Storage allocation mechanisms manage the
allocation and deallocation of memory for objects, with distinct lifetimes for objects and their name

Unit: 3 - Names, Scope, and Bindings 4


DCA1209: Principle of Programming Languages

bindings. Scope rules define the region within a program where a name-to-object binding is valid, and
the complete set of bindings at any point is known as the current referencing environment.

Additional complexities in naming include aliasing, where multiple names refer to the same object;
overloading, where a single name refers to different objects depending on the context and
polymorphism, where an object can have multiple types based on the context or execution history.
Understanding these concepts is important for mastering high-level programming languages and
designing efficient, maintainable, and portable code.

This unit is important for learners to understand how programming languages manage variable
names, scope rules, and memory allocation, which are fundamental for writing efficient and error-
free code. Learners have to focus on each topic, like scopes and binding times, to grasp how variables
interact within different program contexts and review object lifetime and storage management
techniques to optimise memory use. This knowledge will enhance your coding skills and deepen
learner's knowledge of language implementation intricacies.

1.1. Objectives
After studying this unit, you should be able to:

• Define the terms name, scope, and binding time.


• Identify the different types of variable scopes in
programming languages.
• Explain the significance of names in
programming languages and how they reference
data and functions.
• Describe the concept of scope and how it affects
variable visibility and lifespan.
• Illustrate the different times at which binding can occur, such as compile time and runtime.
• Apply the concepts of static, stack-based, and heap-based allocation in a given programming
scenario.

Unit: 3 - Names, Scope, and Bindings 5


DCA1209: Principle of Programming Languages

2. NAMES
A name is a sequence or string of characters utilised to identify an entity within a program, such as a
variable, function, type, or module. Names are essential for readability and maintainability, as they
allow programmers to refer to entities using descriptive identifiers rather than low-level memory
addresses.

Length of Names
There are specific guidelines regarding the allowable length of names in every programming language:

• Fortran 95+ permits using names that can be as long as 31 characters.


• In C99, there is no specific limit on the length of internal names. However, only the first 63
characters of the name are considered significant. The maximum length for external names is
31 characters.
• Java, C#, and Ada have no maximum length restriction, and every character in these
programming languages is considered important.
• C++ does not have a specific length limit, however several implementations may enforce their
own limits.

Form of Names
In most programming languages, names generally follow a common structure:

• Typically, start with a letter.


• Then, they are followed by digits, letters, and underscore characters (_).

Naming Conventions
Underscores were frequently employed in names during the 1970s and 1980s, but their popularity
has decreased. Camel case notation has mostly replaced this approach in languages based on C where
the first word is lowercase, and subsequent words are capitalised (e.g., myStack).

Special Characters in Names


Different languages use special characters to convey additional information about variables:

• PHP: Names of the Variables must start with a dollar sign ($).
• Perl: the initial unique symbol or special character of a variable name ($, @, or %) indicates
its type in a context-specific sense.

Unit: 3 - Names, Scope, and Bindings 6


DCA1209: Principle of Programming Languages

• Ruby: the initial unique symbol or special character of a variable name (@ or @@) indicates
instance variables or class variables, respectively.

In numerous computer languages, particularly those originating from the C family history, the
distinction between uppercase and lowercase letters in names are treated as different, making these
languages case-sensitive. For example, in C++, pen, PEN, and Pen are three different identifiers. This
can negatively impact readability because names that look similar can refer to different entities,
violating the principle that similar-looking constructs should have similar meanings. While some
languages like C mitigate this by conventionally using lowercase for variable names, languages like
Java and C# face more challenges due to predefined names using mixed cases. For instance, Java's
parseInt method must be written exactly as such, and variants like ParseInt or parseint will not be
recognized. This case sensitivity issue primarily affects writability, making it harder to write correct
programs as programmers must remember the exact case usage, a rigidity enforced by the compiler.

Special Words:
Programming languages have special words that make them easier to read by pointing out actions
and telling the difference between the syntax of sentences and programmes. Most of the time, these
unique words are called reserved words or keywords.

• Reserved Words: Programmers cannot redefine these. The language uses them exclusively to
avoid confusion. Reserved words are considered a better design choice because their
meanings are fixed, preventing potential misunderstandings. For example, most languages
cannot use reserved words as variable names.
• Keywords: Keywords are predefined, reserved words in programming languages that have
special meanings and are used to perform specific operations or declare specific constructs.
These words cannot be redefined or used for purposes other than their intended functionality,
as doing so would conflict with the syntax and semantics of the language.

A major problem with reserved words is that an excessive number of them can make it challenging
for programmers to create variable names that aren't reserved.

For example, COBOL has around 300 reserved words, including commonly used names like LENGTH
and COUNT, making it challenging to avoid conflicts.

Variables:
A program variable is a conceptual representation of a computer memory cell or a group of cells.
While programmers commonly see variable names as references to memory locations, variables have

Unit: 3 - Names, Scope, and Bindings 7


DCA1209: Principle of Programming Languages

a broader scope than simple names. The transition from machine languages to assembly languages
substituted numeric memory locations with names, improving programmes' readability and
maintainability.

Six attributes can define a variable: name, address, value, type, lifetime, and scope.

Names

Scope Address

Variables
Lifetime Value

Types

Name: The variable's name serves as the identifier that programmers use to reference the variable
within the code. This name acts as a symbolic representation, enabling programmers to refer to
memory locations in an easily understandable way.

For example, in the statement int age = 25; age is the variable's name.

Address: A variable's address refers to its location in the computer's memory, which can change
while the program is running.

For example, local variables within subprograms can receive different addresses with each call
because they are assigned from the runtime stack. This concept is known as instantiation, i.e., the
same variable can have different addresses at different times. The variable's address, or its l-value, is
important when the variable's name is on the left of an assignment statement.

Multiple variables can assign a similar address, creating aliases. Aliasing occurs when different
variable names refer to a similar memory location. This can hinder readability and complicate
program verification because changes to one alias affect all others.

For example, if sum and total are aliases, modifying sum also affects total. Remembering all aliases
and their implications can be challenging for the programmer, making code harder to read and verify.

Unit: 3 - Names, Scope, and Bindings 8


DCA1209: Principle of Programming Languages

Type: A type of a variable specifies the range of values it can hold and the actions that can be executed
on those values. For instance, in Java, the int type can hold values between -2147483648 and
2147483647, allowing arithmetic operations like addition, subtraction, multiplication, division, and
modulus.

Value: The variable’s value corresponds to the data stored in the memory cell or cells linked to that
variable. Understanding computer memory becomes more accessible when it is conceptualised as
abstract cells rather than real ones. Physical memory cells are commonly byte-sized, consisting of
eight bits, which are frequently insufficient in size for most programme variables.

An abstract memory cell is the minimum amount of memory required to store a variable. For instance,
a floating-point value may physically occupy four bytes, but it is considered to occupy only one
abstract memory cell. Each basic, unorganised category is considered to occupy a singular abstract
entity.

This value is often referred to as the variable's r-value, required when the variable's name is used on
the right side of an assignment statement. To retrieve the r-value, the l-value, or the variable's address,
must be identified first, which can involve a complicated procedure.

Lifetime: A variable's lifetime refers to the span of time during which it occupies memory. This
duration starts when the variable is allocated and ends when it is deallocated. For instance, a local
variable within a function exists only while that function is running. Once the function finishes, the
variable is destroyed, and its memory is released.

Scope: The area of code a variable can access is called its scope.

Unit: 3 - Names, Scope, and Bindings 9


DCA1209: Principle of Programming Languages

3. SCOPES
The scope of a variable is the set of statements in which the variable is accessible and can be
referenced, thereby allowing it to be used within those statements.

The scope rules of a programming language determine how each occurrence of a name (variable) is
associated with its declaration. In functional languages, these rules also determine how names are
linked to expressions.

An important function of scope rules is establishing a relationship between the declarations and
attributes of variables declared outside the presently running subprogram or block.

Declaring a variable inside a program unit or block context makes it local. Conversely, variables that
are declared outside of a programme unit or block yet visible within it are called nonlocal variables.
Among nonlocal variables, global variables stand out due to their program-wide accessibility.

3.1. Static Scoping:


Static scoping, first implemented in ALGOL 60, is a technique for linking names to nonlocal variables,
which has been extensively adopted by numerous imperative and non-imperative programming
languages.

In static scoping, the scope of a variable can be determined before program execution. This allows
both human readers and compilers to identify the type of each variable by examining the source code,
enhancing readability and predictability.

Static-scoped languages are categorised into two groups: those that permit nested subprograms,
creating nested static scopes, and those that do not. A few languages (such as Ada, JavaScript, Common
LISP, Scheme, Fortran 2003+, F#, and Python) permit nested subprograms, while C-based languages
do not.

In static-scoped languages that support nested subprograms, determining a variable's attributes


involves locating its declaration, either explicitly or implicitly. For instance, if a variable x is
referenced in subprogram sub1, the search for its declaration starts within sub1. If not found, the
search moves to the subprogram that declared sub1 (its static parent) and continues up the chain of
enclosing subprograms (static ancestors) until the declaration is found or all enclosing units have
been searched. If the declaration is not found, an undeclared variable error is reported. This

Unit: 3 - Names, Scope, and Bindings 10


DCA1209: Principle of Programming Languages

hierarchical search process ensures that the correct variable is accessed in nested subprograms,
maintaining the integrity of variable scopes.

Example Program 1: Consider a JavaScript function, big, which contains two nested functions, sub1
and sub2:
function big() {
function sub1() {
var x = 7;
sub2();
}
function sub2() {
var y = x;
}
var x = 3;
sub1();
}

In the above code i.e., example program 1, the variable x referenced in sub2 is linked to the x defined
in big. This is because the search for x starts in sub2, and when it isn't found there, the search
continues in the static parent, big. The x in sub1 is ignored since sub1 is not a static ancestor of sub2.

In static scoping languages, some variable declarations can be obscured from certain code segments.
For example, in big, x is declared in both big and sub1. Within sub1, any reference to x points to the
local x, thus hiding the x in big.

In Ada, variables hidden in ancestor scopes can be accessed using qualified references that specify the
ancestor scope's name. If big were written in Ada, the x in big could be accessed in sub1 by using big.x.

Blocks:
Many programming languages enable the creation of new static scopes within executable code. This
idea was introduced in ALGOL 60, allowing specific code segments to possess their own local
variables with limited scope, which are usually allocated dynamically upon entry and deallocated
upon exit. Such sections, called blocks, are fundamental to block-structured languages.

In C-based languages, any compound statement can create a new scope, called a block, by using
matched braces for declarations.

Unit: 3 - Names, Scope, and Bindings 11


DCA1209: Principle of Programming Languages

For example:
if (list[i] < list[j]) {
int temp;
temp = list[i];
list[i] = list[j];
list[j] = temp;
}

Here, the scope of temp is limited to the block defined by the braces. Blocks can be nested within
larger blocks, with variable references resolved by searching enclosing scopes.

In a C function, a nested block can hide variables from an outer block:


void sub() {
int count;
// ...
while (...) {
int count;
count++;
// ...
}
// ...
}

In this example, the count variable in the while loop hides the count variable in the function sub.

Declaration Order:
In older versions of C, such as C89 and some other programming languages, all variable declarations
within a function must be placed at the beginning of the function. Other languages like C99, C++, Java,
JavaScript, and C# permit variable declarations to occur at any point within a program unit, provided
that it is permissible to execute a statement.

Variable declarations in these languages can create scopes that are not essentially associated with
compound statements or subprograms.

For example, in C99, C++, and Java, a local variable's scope begins when it is declared and continues
until the conclusion of the block containing its declaration.

Unit: 3 - Names, Scope, and Bindings 12


DCA1209: Principle of Programming Languages

But, in C#, the scope of a variable declared within a block encompasses the entire block, regardless of
where the declaration occurs, provided it is not within a nested block. Even with this, C# still
mandates that variables must be declared before they are used in the code. Therefore, even though
the scope extends to the top of the block, a variable cannot be utilised before its declaration.

You can declare local variables anywhere in JavaScript code, but their scope will always cover the
entire function. If a variable is used before its declaration, it is assigned the value undefined.

Additionally, in the ‘for’ statements of C++, Java, and C#, variables can be defined within the
initialization expressions. Initially, in C++, the scope of such variables extended to the end of the
smallest enclosing block. But, in the standardized version of C++, as well as in Java and C#, the scope
is limited to the construct itself.

Example:
void fun() {
// ...
for (int count = 0; count < 10; count++) {
// ...
}
// ...
}

In the given code, the function fun includes a for loop with a variable declaration within its
initialization expression. This is a common practice in modern versions of C++, Java, and C#. The
variable count is declared and initialized within the for loop's initialization section (int count = 0). In
these languages, the scope of count is limited to the for loop itself. This means count is only accessible
and usable within the loop's body and not outside of it. This approach helps in keeping the scope of
variables minimal, reducing the likelihood of errors from variable misuse or unintended interactions.
The rest of the function can contain other code, but count cannot be referenced outside the for loop.

Global Scope:
Some programming languages like C, C++, PHP, JavaScript, and Python allow variables to be defined
outside functions, creating global variables that can be accessed by those functions. In C and C++,
global variables require both declarations and definitions, where declarations specify types and
attributes without allocating storage, and definitions allocate storage. Multiple declarations can exist,
but only one definition is allowed. A variable declared outside functions in one file is defined in

Unit: 3 - Names, Scope, and Bindings 13


DCA1209: Principle of Programming Languages

another. A global variable can be hidden by a local variable with the same name, but it can still be
accessed using the extern keyword or scope resolution operator (::) in C++.

In PHP, variables are implicitly declared when they appear in statements. Variables declared outside
any function are global and can be accessed in functions using the $GLOBALS array or a global
declaration.

For Example, in the following PHP code:


$day = "Monday";
$month = "January";
function calendar() {
$day = "Tuesday";
global $month;
print "local day is $day <br />";
$gday = $GLOBALS['day'];
print "global day is $gday <br />";
print "global month is $month <br />";
}
calendar();

The given PHP code demonstrates the use of global and local variables within a function and
illustrates how to access global variables when a local variable with the same name exists. Initially,
two global variables, $day and $month, are defined with the values "Monday" and "January,"
respectively. The calendar function is then defined, within which a local variable $day is assigned the
value "Tuesday." This local variable masks the global $day within the function's scope.

To access the global $month variable within the function, the global keyword is used, making $month
accessible and allowing its value to be printed. Inside the function, print "local day is $day <br />";
outputs "local day is Tuesday," reflecting the value of the local $day.

Next, the global $day is accessed through the $GLOBALS array, a PHP superglobal that contains
references to all global variables. The statement $gday = $GLOBALS['day']; retrieves the value of the
global $day ("Monday") and assigns it to $gday. The line print "global day is $gday <br />"; then prints
"global day is Monday." Finally, the function prints the global $month using print "global month is
$month <br />";, resulting in "global month is January." When the calendar function is called, the
output is:

Unit: 3 - Names, Scope, and Bindings 14


DCA1209: Principle of Programming Languages

The output will be:


local day is Tuesday
global day is Monday
global month is January

In JavaScript, global variables work similarly to PHP but cannot be accessed if a local variable with
the same name exists in a function. Python's handling of global variables is unique; variables are
declared when assigned values. To modify a global variable within a function, it must be declared as
global using the global keyword.

For example:
day = "Monday"
def tester():
global day
print("The global day is:", day)
day = "Tuesday"
print("The new value of day is:", day)
tester()

The function tester is defined to demonstrate how to reference and modify this global variable within
a function. When the tester function is called, it first uses the global keyword to specify that it is
referring to the global variable day. This allows the function to access and modify the global variable
instead of creating a new local variable. The function then prints the current value of day, which is
"Monday". After that, it updates the value of day to "Tuesday" and prints this new value.

The output will be:


The global day is: Monday
The new value of day is: Tuesday

3.2. Dynamic Scope:


Dynamic scoping determines the scope of variables based on the calling sequence of subprograms
rather than their spatial arrangement, i.e., the scope is established at run-time.

function big() {
function sub1() {
var x = 7;
}

Unit: 3 - Names, Scope, and Bindings 15


DCA1209: Principle of Programming Languages

function sub2() {
var y = x;
var z = 3;
}
var x = 3;
}

In the above code, the function big which contains two nested functions sub1 and sub2. Within sub1,
a local variable x is declared and initialized to 7. In sub2, another variable y is assigned the value of x,
and z is set to 3. With dynamic scoping, the reference to x in sub2 can refer to either x from sub1 or
the outer x depending on the calling sequence of these functions.

Dynamic scoping works by searching for variable declarations starting from the local scope and
moving up through the calling sequence of functions. If a variable is not found in the local declarations,
the search continues in the calling function, and then in that function's caller, and so on, until a
declaration is found or a runtime error occurs if none is found.

Unit: 3 - Names, Scope, and Bindings 16


DCA1209: Principle of Programming Languages

4. CONCEPT OF BINDING TIME


In computer programming, a binding is the relationship between an object's attributes and that object
or entity.

This can include:


• The association between a variable and its type or value.
• The association between an operation and a symbol (e.g., the asterisk (*) symbol being
associated with the multiplication operation).

Binding time is the moment when a binding takes place.

It occurs at different stages:


Language Language Compile Link Time Load Time Run Time
Design implementa Time
Time tion Time

• Language Design Time: When the language is being designed, certain symbols and operations
are defined. For example, the asterisk (*) is bound to the multiplication operation.
• Language Implementation Time: During the implementation of the language, specific details
are decided. For instance, the data type int in C is bound to a range of possible values.
• Compile Time: When the source code is compiled, variables are connected to their data types.
For example, during compilation in Java, a variable is assigned a particular data type.
• Link Time: When different modules of a program are linked together, references to library
subprograms are resolved.
• Load Time: Variables can be linked to specific storage places when the programme is loaded into
memory.
• Run Time: During the execution of the program, some bindings occur dynamically, such as the
binding of variables declared within methods in Java.

Example:
Let’s consider the following Java Statement:
Sum = Sum + 10;
Various bindings and their binding times in this statement are:

• Type of count: determined at compile time.


• Set of possible values for Sum: Fixed at compiler design time.
• Meaning of the + operator: Established at compile time, based on the types of its operands.

Unit: 3 - Names, Scope, and Bindings 17


DCA1209: Principle of Programming Languages

• Internal representation of the literal 10: Fixed at compiler design time.


• Value of Sum: Determined at execution time when the statement is executed.

4.1. Binding of Attributes to Variables


A binding is regarded as static if it is set before the program begins to run and remains unchanged
throughout its execution. In contrast, a binding is dynamic if created during runtime or can change
while the program runs. The physical binding of a variable with a storage cell in a virtual memory
setting is intricate, as the memory page or segment housing the cell may be swapped in and out of
memory several times while the program runs. Despite this complexity, these bindings are handled
by the computer hardware and remain hidden from both the program and the user.

4.2. Type Bindings


Before a variable can be used in a program, it must be linked to a data type. This process, called type
binding, involves deciding the manner and timing of the type specification. Type bindings can be
determined statically via explicit or implicit declarations.

i. Static Type Binding


Explicit Declarations: An explicit declaration involves a direct statement in the program that lists
variable names and specifies their types. This method is common in many programming languages
designed since the mid-1960s, which require explicit declarations for all variables. Examples of
such languages include Perl, JavaScript, Ruby, and ML, which are exceptions to this rule.

Implicit Declarations:
Instead of using explicit declaration lines, implicit declarations use default rules to link variables
and types. In this case, a variable is implicitly declared when it appears for the first time in a
programme. The language processor will use this method. The language processor can be either a
compiler or an interpreter. Naming rules are a simple way to support implicit type linking. In
Fortran, for example, an identifier that isn't stated directly is declared based on its first letter.
Some identifiers, like those that start with I, J, K, L, M, or N (or their lowercase versions), are
automatically declared as Integer types. Other types are stated as Real types.

Implicit Declarations and Reliability


While implicit declarations offer convenience, they can compromise reliability by preventing the
compilation process from catching typos and mistakes made by programmers. For example, in
Fortran, undeclared variables are given default types and attributes, which can lead to subtle and

Unit: 3 - Names, Scope, and Bindings 18


DCA1209: Principle of Programming Languages

hard-to-diagnose errors. To avoid such issues, many Fortran programmers use the declaration
Implicit none, instructing the compiler not to implicitly declare any variables.

Special Characters and Namespaces


Some languages mitigate the problems associated with implicit declarations by requiring specific
types to begin with particular special characters. For instance, in Perl, variable names beginning
with $ denote scalars, @ denotes arrays, and % denotes hash structures. This creates distinct
namespaces for different types, so the names ‘@apple’ and ‘%apple’ are unrelated. This approach
ensures that the type of a variable is always clear from its name, which differs from Fortran's
approach, where type cannot always be inferred from the variable's name alone.

Context-Based Implicit Type Declarations


Another form of implicit type declaration, known as type inference, uses the context in which a
variable appears to determine its type. For instance, in C#, a ‘var’ declaration must include an
initial value, which determines the variable's type.

Consider the following examples:


var sum = 0; // sum is an int
var total = 0.0; // total is a float
var name = "Alfred"; // name is a string
where,
Total is an int type, sum is a float type, and name is a string type.
These variables are statically typed, meaning their types are fixed for the duration of their
scope.

ii. Dynamic Type Binding


Dynamic type binding is the procedure in which the type of a variable is not explicitly declared in
a declaration statement or determined by the spelling of its name. Instead, the type of the variable
is determined when a value is assigned to it in an assignment statement. Throughout this
assignment, the variable assumes the type of the value found on the right-hand side of the
expression. This task may also entail associating the variable with a specific location in memory,
as different data kinds may necessitate different amounts of storage space. As a result, it is possible
to assign any value of any type to a variable, and the type of a variable can change numerous times
while the programme is running. It is essential to acknowledge that the type of a dynamically
bound variable is just transient.

Unit: 3 - Names, Scope, and Bindings 19


DCA1209: Principle of Programming Languages

When a variable's type is statically bound, its name is permanently linked to that type. However,
in the context of dynamic type binding, the name is simply temporarily linked to the type. Names
are inherently associated with variables, which in turn are associated with types. Language
systems that employ dynamic type binding function in a significantly distinct manner compared
to those that utilise static type binding.

Dynamic type binding offers enhanced programming freedom as its primary feature. For example,
in a dynamically typed programming language, a programme intended to manipulate numerical
data can be constructed in a generic manner to handle any sort of numeric data. In dynamically
typed languages, the variables automatically adjust their type based on the provided data,
compared to statically typed languages where the data type must be predetermined.

Before the mid-1990s, most programming languages used static type binding, with exceptions like
some functional languages such as LISP. Since then, there has been a significant shift toward
languages that employ dynamic type binding, such as Python, Ruby, JavaScript, and PHP.

For example, in JavaScript, assigning a list of numbers to a variable changes its type to an array,
and reassigning a single number changes it to a scalar. This flexibility allows for dynamic
adjustments to variable types based on the data they hold i.e.,

list = [10.2, 3.5];

Regardless of the previous type of the variable list, this assignment makes list the name of a single-
dimension array of length 2. If we then execute:

list = 47;

This demonstrates how dynamic type binding allows variables to adapt their types at runtime
based on the assigned value.

C# added dynamic type binding in its 2010 edition, enabling increased flexibility in managing
variables with unknown types until runtime. In the C# programming language, a variable can be
declared to utilise dynamic type binding by utilising the 'dynamic' keyword:

dynamic any;

This declaration is similar to declaring a variable of type object, as it allows any to be assigned a
value of any type. However, there are important differences. While an object type can hold any
data type, dynamic is particularly useful for interoperability with dynamically typed languages,
such as IronPython and IronRuby, which are .NET versions of Python and Ruby, respectively.

Unit: 3 - Names, Scope, and Bindings 20


DCA1209: Principle of Programming Languages

With dynamic, you may effortlessly handle data of unknown types originating from other sources.
This functionality is particularly useful in situations where the data type cannot be identified
during the compilation process. The dynamic keyword in C# allows for its usage in several
contexts such as class members, properties, method parameters, method return values, and local
variables.

In pure object-oriented languages such as Ruby, all variables are references and do not possess
fixed types. Instead, all data is represented as objects, and any variable can be used to refer to any
object. In other words, all variables are of the same type, which is a reference. This is distinct from
languages such as Java, where references are limited to a certain type of value. Ruby allows variables
to reference any object, offering considerable versatility.

However, dynamic type binding has its disadvantages.


• Firstly, it can make programs less reliable, as the compiler's ability to detect errors is reduced
compared to statically typed languages. Dynamic binding enables the assignment of values of
any type to any variable., so type mismatches are not caught at compile time. For example, an
incorrect assignment in JavaScript changes the variable's type without error detection,
potentially leading to runtime issues.
• Secondly, implementing dynamic-type binding has a high cost, particularly in terms of
execution time. Runtime type verification requires a runtime descriptor for each variable to
track its current type, increasing the program's overall complexity and execution time.

Finally, languages with dynamic type binding are often implemented using pure interpreters rather
than compilers, as the types of variables are not known at compile time. Pure interpretation can be
significantly slower than executing compiled machine code. Although the time taken for dynamic type
binding is hidden within the overall interpretation time, it still adds to the performance overhead. In
contrast, statically typed languages are typically compiled into efficient machine code, resulting in
faster execution.

Unit: 3 - Names, Scope, and Bindings 21


DCA1209: Principle of Programming Languages

5. OBJECT LIFETIME AND STORAGE MANAGEMENT


Object lifetime refers to the duration for which an object exists in memory during a program's
execution. This concept is crucial because it impacts memory usage, performance, and the overall
behaviour of the program. The lifetime begins when the object is created and ends when it is no longer
accessible or needed, allowing its allocated memory to be reclaimed.

It is essential to differentiate between the names and the objects they refer to. Key events in this
context include:

• Creation of Objects: Allocating memory and initialising a new object.


• Creation of Bindings: Establishing a connection between a name and an object.
• References to Variables, Subroutines, and Types: Using these bindings to access the associated
objects.
• Deactivation and Reactivation of Bindings: Temporarily making and restoring bindings
unusable.
• Destruction of Bindings: Severing the connection between names and objects.
• Destruction of Objects: Releasing the memory and resources used by an object.

The period between the creation and destruction of a name-to-object binding is known as the
binding's lifetime, while the time from the creation to the destruction of an object is its lifetime. These
lifetimes may not always align. An object can persist and remain accessible even if the name bound to
it is no longer valid.

For example, when a variable is passed by reference to a subroutine, the binding between the
parameter name and the variable has a shorter lifetime than the variable itself. On the other hand, a
binding may survive the object it references, leading to a dangling reference, which often indicates a
programming error. This can occur if an object allocated with C++'s new operator is passed by
reference and then deallocated (delete-ed) before the subroutine completes, leaving a binding to a
non-existent object.

Object lifetimes generally align with three main storage allocation mechanisms, which manage the
space for objects:

• Static Objects: These objects are assigned a fixed address that remains constant throughout
the program's execution.

Unit: 3 - Names, Scope, and Bindings 22


DCA1209: Principle of Programming Languages

• Stack Objects: Allocated and deallocated in a last-in, first-out (LIFO) manner, stack objects are
typically associated with subroutine (function) calls and returns i.e. when a function is called,
its local variables are pushed onto the stack, and when the function exits, they are popped off,
making stack management fast and efficient.
• Heap Objects: These objects can be allocated and deallocated anytime, providing flexibility
but requiring more complex and costly storage management algorithms. The heap is used for
dynamic memory allocation, allowing objects to persist beyond the scope of a single function,
but necessitating careful management to avoid fragmentation and memory leaks.

5.1. Static Allocation


Static allocation refers to the assignment of fixed memory addresses to objects that remain constant
throughout the program's execution. This is commonly applied to global variables, but also includes
other elements such as machine-language instructions, numeric and string constants, and various
runtime tables used for debugging, type checking, and exception handling. These statically allocated
objects are often placed in protected, read-only memory to prevent accidental modification, which
would otherwise cause a runtime error.

For example, in languages like Fortran, where recursion was initially unsupported, static allocation
can be used for local variables within subroutines. Since only one subroutine invocation can be active
at a time, the same memory locations can be reused for local variables across multiple calls,
eliminating the need for runtime allocation and deallocation. This method reduces overhead and
improves performance.

Many programming languages require constants to have values that can be determined at compile
time. These constants can be statically allocated, even if local to a recursive subroutine, because
multiple instances can share the same memory location. On the other hand, languages like C and Ada
allow constants whose values depend on runtime variables. Such constants must be allocated on the
stack if they are local to recursive subroutines.

Unit: 3 - Names, Scope, and Bindings 23


DCA1209: Principle of Programming Languages

Figure 1: Memory Layout for Static Allocation in Non-Recursive Subroutines.

Other than local variables and constants, compilers also manage other information associated with
subroutines, as shown in figure 1. This includes:

• Arguments and Return Values: Stored in registers, when possible, but sometimes require
memory space.
• Temporaries: Intermediate values from complex calculations, ideally kept in registers.
• Bookkeeping Information: Includes return addresses, references to caller stack frames,
saved registers, and debugging data.

5.2. Stack-Based Allocation


When a programming language supports recursion, static allocation of local variables is impractical
because the number of instances that might exist simultaneously is theoretically unbounded. Instead,
the natural nesting of subroutine calls facilitates the use of a stack to allocate space for local variables
dynamically. Each subroutine instance at runtime has its own stack frame, or activation record,
containing arguments, return values, local variables, temporaries, and bookkeeping information.

Stack Frame Structure


The stack frame for each subroutine includes several components as shown in Figure 2:

Unit: 3 - Names, Scope, and Bindings 24


DCA1209: Principle of Programming Languages

• Arguments and Return Values: Located at the top of the frame for easy access by the called
subroutine.
• Local Variables: Stored within the frame for the subroutine's use.
• Temporaries: Intermediate values needed during calculations.
• Bookkeeping Information: Includes the return address, references to the caller's stack frame
(dynamic link), and saved registers.

The stack-based allocation method is maintained by the calling sequence, which includes code
executed by the caller before and after the call, and the prologue and epilogue of the subroutine itself.
This sequence ensures that each subroutine's frame is correctly managed on the stack.

Figure 2: Stack-Based Allocation of Space for Subroutines with Recursion

The figure 2 illustrates the stack-based allocation of space for subroutines in a programming
environment. In this setup, each active subroutine call at runtime occupies a distinct frame on the
stack. The stack grows downward, towards lower memory addresses, as indicated by the arrow on
the left.

• Subroutine Frames: Each subroutine (A, B, C, and D) has its own frame on the stack. When a
subroutine is called, a new frame is pushed onto the stack. This frame contains the subroutine’s
local variables, temporaries, arguments, return values, and miscellaneous bookkeeping
information, including the return address.

Unit: 3 - Names, Scope, and Bindings 25


DCA1209: Principle of Programming Languages

• Frame Pointers: The frame pointer (fp) points to a known location within the frame of the
current subroutine, facilitating access to local variables and arguments. The stack pointer (sp)
points to the top of the stack, marking the next available location for a new frame.
• Call Sequence: The figure shows Subroutine A calling Subroutine B, which calls Subroutine C,
and so on. When Subroutine A calls B, the arguments and return address are placed at the top
of A's frame. Subroutine B's frame is then pushed onto the stack, containing its own local
variables and bookkeeping data. This process continues with each nested call adding a new
frame to the stack.
• Accessing Variables: Within each frame, local variables and temporaries are accessed using
offsets from the frame pointer. Arguments for called routines are positioned at the top of the
frame for easy access by the callee.

Advantages of Stack Allocation:


Even in non-recursive languages, using a stack for local variables can be beneficial. It reduces memory
usage because the total space needed for local variables of currently active subroutines is typically
much smaller than the total space required if all subroutines were allocated statically. This efficiency
is due to the fact that not all subroutines are active simultaneously.

5.3. Heap-Based Allocation


Heap-based allocation is a memory management strategy where storage is dynamically allocated and
deallocated as needed. This method is essential for data structures that grow and shrink during
program execution, such as linked lists, dynamically resized arrays, and other complex structures like
sets and strings.

External Fragmentation:
The figure 3 explains the concept of external fragmentation in heap-based allocation. In the figure 3:

• Shaded Blocks represent memory blocks currently in use by the program.


• Clear Blocks represent free memory blocks available for allocation.
• Allocation Request: The block below the heap shows a memory allocation request that
cannot be satisfied despite the total free space being sufficient.

Unit: 3 - Names, Scope, and Bindings 26


DCA1209: Principle of Programming Languages

Figure 3: External Fragmentation

External fragmentation occurs when free memory is divided into small, non-contiguous blocks,
making it impossible to allocate a large block even if enough total free space exists. This scenario
happens because the free blocks are scattered throughout the heap, preventing the allocation of a
single contiguous block of the required size.

Managing the Heap


To manage space in a heap, different strategies are used:

• Single Free List: A common approach is to maintain a single linked list of free blocks. When a
memory request is made, the list is searched to find a suitable block.
o First Fit: Allocates the first block large enough to satisfy the request.
o Best Fit: Searches the entire list to find the smallest block that fits the request. While it can
better preserve large blocks for large requests, it increases allocation time and often leads
to many small leftover blocks.

• Fragmentation:
o Internal Fragmentation: Occurs when a block larger than necessary is allocated, leaving
unused space within the block.
o External Fragmentation: Occurs when free space is scattered, preventing the allocation
of large contiguous blocks.

Some algorithms maintain separate free lists for different block sizes to moderate allocation costs.
These lists help manage memory more efficiently by pooling blocks of similar sizes together.

Unit: 3 - Names, Scope, and Bindings 27


DCA1209: Principle of Programming Languages

6. SUMMARY
In this unit, learners have studied the concept of name identifiers used to represent variables,
functions, types, and other entities within a program. These names form the basis for referencing and
manipulating data and functions. Understanding the rules and conventions for defining and using
names is important for writing clear and effective code.

Learners have studied the concept of scope, which defines the context within which a name is valid
and can be used. Scopes help manage the visibility and lifespan of variables and functions, ensuring
that names are appropriately accessible and preventing conflicts. Scope rules are fundamental to
resource management and program organization, enabling developers to create modular and
maintainable code.

Binding time refers to the point at which various attributes, such as values, storage locations, and
types, are associated with program entities. Understanding when and how these bindings occur is
important for understanding how programs execute and manage resources. Binding can happen at
different stages, including compile time and runtime, influencing program flexibility and performance.

Object lifetime and storage management are key to efficient memory use in programming. Static
allocation involves assigning fixed memory addresses to objects, providing efficiency but lacking
flexibility. In contrast, stack-based allocation uses a stack data structure to manage memory
dynamically, supporting subroutine calls and recursion. Heap-based allocation allows for dynamic
memory allocation and deallocation at any time, essential for handling complex data structures and
varying object sizes.

Unit: 3 - Names, Scope, and Bindings 28


DCA1209: Principle of Programming Languages

7. SELF-ASSESSMENT QUESTIONS
Multiple choice Questions
1 What is the primary purpose of variable names in programming languages?
A) To represent memory addresses
B) To provide human-readable identifiers
C) To execute loops
D) To define functions
2 Which of the following is an example of a variable scope?
A) Global
B) Local
C) Both a and b
D) None of the above
3 When is a variable's type typically bound in a statically typed language?
A) Runtime
B) Compile time
C) Load time
D) Execution time
4 Which storage allocation method allows variables to retain their values between function
calls?
A) Stack-based allocation
B) Heap-based allocation
C) Static allocation
D) Dynamic allocation
5 In which type of allocation are variables typically stored in a last-in, first-out order?
A) Static allocation
B) Heap-based allocation
C) Stack-based allocation
D) Dynamic allocation
Fill in the Blanks:

6 The _______ of a variable defines where the variable can be accessed in the program.

7 The process of associating a value or type with a variable is called _______.

Unit: 3 - Names, Scope, and Bindings 29


DCA1209: Principle of Programming Languages

8 The _______ of a variable is the period during which it exists in memory.

True or False:

9 Heap-based allocation can lead to memory leaks if not properly managed.

10 The binding time of a variable's type in statically typed languages is at runtime.

Unit: 3 - Names, Scope, and Bindings 30


DCA1209: Principle of Programming Languages

8. GLOSSARY
Financial Management is concerned with the procurement of the least cost funds, and its effective

Identifiers refer to variables, functions, types, and other entities in a


Names -
program.

The region of a program where a particular variable or function is


Scope -
accessible.

Binding Time - When a variable is bound to a type, value, or storage location.

Memory allocation that occurs before program execution and remains


Static Allocation -
fixed.

Stack-Based Memory allocation that uses a stack data structure to manage the storage
-
Allocation of local variables.

Heap-Based Memory allocation that allows for dynamic allocation and deallocation of
-
Allocation memory blocks.

The duration a variable or object exists in memory during program


Lifetime -
execution.

A phenomenon where free memory is broken into small, non-contiguous


Fragmentation -
blocks, making it difficult to allocate large contiguous memory blocks.

A data structure that stores information about a single execution of a


Activation Record - subroutine, including local variables, return addresses, and temporary
values.

A register that points to the start of the current stack frame, used to access
Frame Pointer -
function parameters and local variables.

Unit: 3 - Names, Scope, and Bindings 31


DCA1209: Principle of Programming Languages

9. TERMINAL QUESTIONS
1. Explain the difference between static and dynamic scoping with examples.
2. Describe the concept of binding time and its significance in programming languages.
3. Explain the concept of variable lifetime and its impact on program execution.
4. How does stack-based allocation work? Explain with an example.
5. What is heap-based allocation and what are its primary uses?
6. What is the role of the frame pointer in stack-based memory allocation?
7. Explain the importance of scope in programming languages with suitable examples.
8. Explain the concept of object lifetime and its relationship with storage management strategies in
programming languages.
9. What are the key differences between lexical and dynamic scoping, and how do they affect variable
binding and program execution?
10. Provide a detailed analysis of how different storage management techniques (static, stack-based,
and heap-based) are utilized in modern programming languages.

Unit: 3 - Names, Scope, and Bindings 32


DCA1209: Principle of Programming Languages

10. ANSWERS
Self-Assessment Questions
1. To provide human-readable identifiers
2. Both a and b
3. Compile time
4. Static allocation
5. Stack-based allocation
6. Scope
7. Binding
8. Lifetime
9. True
10. False

Terminal Questions Answers


1. Refer to section 3
2. Refer to section 4
3. Refer to section 5
4. Refer to section 5.2
5. Refer to section 5.3
6. Refer to section 5.2
7. Refer to section 3
8. Refer to section 5
9. Refer to section 3
10. Refer to sections 5.1, 5.2, and 5.3

Unit: 3 - Names, Scope, and Bindings 33


DCA1209: Principle of Programming Languages

11. REFERENCES
1. Robert W. Sebesta, “Concepts of Programming Languages”, 2012, Tenth Edition, . ISBN 10: 0-13-
139531-9.
2. Michael L. Scott, “ Programming Language Pragmatics”, 2015, Fourth edition, Morgan Kaufmann
Publishers In, ISBN 13: 978-0-12-633951-2.

Unit: 3 - Names, Scope, and Bindings 34


DCA1209: Principle of Programming Languages

BACHELOR OF COMPUTER APPLICATIONS


SEMESTER 2

DCA1209
PRINCIPLE OF PROGRAMMING
LANGUAGES
Unit: 4 - Implementing Scope and Separate Compilation 1
DCA1209: Principle of Programming Languages

Unit – 4
Implementing Scope and Separate
Compilation

Unit: 4 - Implementing Scope and Separate Compilation 2


DCA1209: Principle of Programming Languages

TABLE OF CONTENTS

Fig No /
SL SAQ / Page
Topic Table /
No Activity No
Graph
1 Introduction - -
4-5
1.1 Objectives - -
2 Scope Rules - -

2.1 Static Scoping - -

2.2 Blocks - -
6 - 13
2.3 Declaration Order - -

2.4 Global Scope - -

2.5 Dynamic Scope - -


3 Binding of Referencing Environments - -

3.1 Subroutine Closures - - 14 - 19

3.2 First- and Second-Class Subroutines - -


4 Separate Compilation - - 20 - 26
5 Summary - - 27
6 Self-Assessment Questions - 1 28 - 29
7 Glossary - - 30 - 31
8 Terminal Questions - - 32
9 Answers - - 33
10 References - - 34

Unit: 4 - Implementing Scope and Separate Compilation 3


DCA1209: Principle of Programming Languages

1. INTRODUCTION
In the previous unit 3, learners studied the fundamentals of programming languages, including the
use of names as identifiers, the concept of scopes for managing visibility and lifetime of entities, and
binding times for associating attributes to variables. They explored different types of type bindings
and learned about object lifetime and storage management, covering static, stack-based, and heap-
based allocation. This foundation prepares them for more advanced topics in the next unit 4.

In this unit, learners will understand the scope rules in programming, which is essential for managing
the visibility and lifetime of variables and functions. Static scoping determines the scope of a variable
based on the program's structure, making it easier to predict variable behaviour. Blocks allow for
defining new scopes within a program, facilitating local variable declaration and management. The
declaration order determines the sequence in which variables and functions must be declared,
ensuring proper usage and avoiding reference errors. Global scope pertains to variables and functions
accessible throughout the entire program, providing a means for shared data. In contrast, dynamic
scope bases variable scope on the program’s execution flow, allowing more flexibility but potentially
causing unpredictable behaviour. The binding of referencing environments is another essential
concept; subroutine closures capture the environment in which a function is defined, preserving
variable bindings for later use. Learners will learn the first- and second-class subroutines, which
differentiate between subroutines that can be passed as arguments or returned from other
subroutines and those that cannot. Learners will also learn that separate compilation manages large
programs by dividing them into smaller, independently compiled modules, enhancing code
modularity and maintainability.

To learn this unit, learners should practice implementing small programs demonstrating different
scope rules and binding environments. Visualizing scope hierarchies and data flow through diagrams
can help them better understand static and dynamic scoping. Using debuggers to step through code
will provide real-time observations of variable scope and lifetime. Writing and compiling separate
modules will offer hands-on experience with separate compilations. Referring to language-specific
documentation is essential to grasp how these concepts are implemented in various programming
languages.

Unit: 4 - Implementing Scope and Separate Compilation 4


DCA1209: Principle of Programming Languages

1.1. Objectives
After studying this unit, you should be able to:

• Define key terms such as static scoping, dynamic


scoping, and subroutine closures.
• Explain the difference between global and local
scope.
• Demonstrating using blocks, static and dynamic
scoping, and separate compilation.
• Develop a modular program using the separate
compilation

Unit: 4 - Implementing Scope and Separate Compilation 5


DCA1209: Principle of Programming Languages

2. SCOPE RULES
The scope of a variable refers to the specific range of statements within a programme where the
variable can be accessed and can be referenced, thereby allowing it to be used within those
statements.

The scope rules of a programming language determine how each occurrence of a name (variable) is
associated with its declaration. In functional languages, these rules also determine how names are
linked to expressions.

An important function of scope rules is establishing a relationship between the declarations and
attributes of variables declared outside the presently running subprogram or block.

Declaring a variable inside a program unit or block context makes it local. Conversely, variables that
are declared outside of a programme unit or block yet visible within it are called nonlocal variables.
Among nonlocal variables, global variables stand out due to their program-wide accessibility.

2.1. Static Scoping


Static scoping, first implemented in ALGOL 60, is a technique for linking names to nonlocal variables,
which numerous imperative and non-imperative programming languages have extensively adopted.

In static scoping, the scope of a variable can be evaluated or determined before program execution.
This allows both human readers and compilers to identify the type of each variable by examining the
source code, enhancing readability and predictability.

Static-scoped languages are categorised into two groups: those that permit nested subprograms,
creating nested static scopes, and those that do not. A few languages (such as Ada, JavaScript, Common
LISP, Scheme, Fortran 2003+, F#, and Python) permit nested subprograms, while C-based languages
do not.

In static-scoped languages that support nested subprograms, determining a variable's attributes


involves locating its declaration, either explicitly or implicitly. For instance, if a variable x is
referenced in subprogram sub1, the search for its declaration starts within sub1. If not found, the
search moves to the subprogram that declared sub1 (its static parent) and continues up the chain of
enclosing subprograms (static ancestors) until the declaration is found or all enclosing units have
been searched. If the declaration is not found, an undeclared variable error is reported. This

Unit: 4 - Implementing Scope and Separate Compilation 6


DCA1209: Principle of Programming Languages

hierarchical search process ensures that the correct variable is accessed in nested subprograms,
maintaining the integrity of variable scopes.

Consider a JavaScript function, big, which contains two nested functions, sub1 and sub2:

function big() {
function sub1() {
var x = 7;
sub2();
}
function sub2() {
var y = x;
}
var x = 3;
sub1();
}

In the above code, the variable x referenced in sub2 is linked to the x defined in big. This occurs
because the search for the value x starts in sub2, and when it isn't located or found there, the search
proceeds in the static parent, big. The x in sub1 is disregarded or ignored since sub1 is not a static
ancestor of sub2.

Some variable declarations from certain code segments can be obscured in static scoping languages.
For example, in big, x is declared in both big and sub1. Within sub1, any reference to x points to the
local x, thus hiding the x in big.

In Ada, variables hidden in ancestor scopes can be retrieved or accessed using qualified references
that specify the ancestor scope's name.

The value of x in big could be retrieved in sub1 using big.x if big were written in Ada.

2.2. Blocks
Many programming languages enable the creation of new static scopes within executable code. This
idea was introduced in ALGOL 60, allowing specific code segments to possess their own local
variables with limited scope, which are usually allocated dynamically upon entry and deallocated
upon exit. Such sections, called blocks, are fundamental to block-structured languages.

Unit: 4 - Implementing Scope and Separate Compilation 7


DCA1209: Principle of Programming Languages

In C-based languages, any compound statement can create a new scope, called a block, by using
matched braces for declarations.

For example:
if (list[i] < list[j]) {
int temp;
temp = list[i];
list[i] = list[j];
list[j] = temp;
}

Here, the scope of temp is limited to the block defined by the braces. Blocks can be nested within
larger blocks, with variable references resolved by searching enclosing scopes.

In a C function, a nested block can hide variables from an outer block:

void sub() {
int count;
// ...
while (...) {
int count;
count++;
// ...
}
// ...
}

In this example, the count variable in the while loop hides the count variable in the function sub.

2.3. Declaration Order


In older versions of C, such as C89 and some other programming languages, all variable declarations
within a function must be placed at the beginning of the function. Other languages like C99, C++, Java,
JavaScript, and C# permit variable declarations to occur at any point within a program unit, provided
that it is permissible to execute a statement.

These languages' variable declarations provide the ability to create or generate scopes that aren't
necessarily linked or associated to subprograms or compound expressions.

Unit: 4 - Implementing Scope and Separate Compilation 8


DCA1209: Principle of Programming Languages

For example, in C99, C++, and Java, the scope of a local variable starts at the time of declaration and
continues until the conclusion of the block containing its declaration.

In C#, the scope of a variable declared within a block extends across the whole block, regardless of
where the declaration is located, as long as it is not within a nested block. However this, C# still
requires that variables be declared before they are used in the code. Therefore, even though the scope
extends to the top of the block, a variable cannot be utilised before its declaration.

You can declare local variables anywhere in JavaScript code, but their scope will always cover the
entire function. If a variable is used before its declaration, it is assigned the value undefined.

Additionally, in the ‘for’ statements of C++, Java, and C#, variables can be defined within the
initialization expressions. Initially, in C++, the scope of such variables extended to the end of the
smallest enclosing block. But, in the standardized version of C++, as well as in Java and C#, the scope
is limited to the construct itself.

Example:
void fun() {
// ...
for (int count = 0; count < 10; count++) {
// ...
}
// ...
}

In the given code, the function fun includes a for loop with a variable declaration within its
initialization expression. This is a common practice in modern versions of C++, Java, and C#. The
variable count is declared and initialized within the for loop's initialization section (int count = 0). In
these languages, the scope of count is limited to the for loop itself. This means count is only accessible
and usable within the loop's body and not outside of it. This approach helps in keeping the scope of
variables minimal, reducing the likelihood of errors from variable misuse or unintended interactions.
The rest of the function can contain other code, but the count cannot be referenced outside the for
loop.

2.4. Global Scope


Some programming languages like C, C++, PHP, JavaScript, and Python allow variables to be defined
outside functions, creating global variables that can be accessed by those functions. In C and C++,

Unit: 4 - Implementing Scope and Separate Compilation 9


DCA1209: Principle of Programming Languages

global variables require declarations and definitions, where declarations specify types and attributes
without allocating storage, and definitions allocate storage. Multiple declarations can exist, but only
one definition is allowed. A variable declared outside functions in one file is defined in another. A
global variable can be hidden by a local variable with the same name, but it can still be accessed using
the extern keyword or scope resolution operator (::) in C++.

In PHP, variables are implicitly declared when they appear in statements. Variables declared outside
any function are global and can be accessed in functions using the $GLOBALS array or a global
declaration.

For Example, in the following PHP code:


$day = "Monday";
$month = "January";
function calendar() {
$day = "Tuesday";
global $month;
print "local day is $day <br />";
$gday = $GLOBALS['day'];
print "global day is $gday <br />";
print "global month is $month <br />";
}
calendar();

The given PHP code demonstrates the use of global and local variables within a function and
illustrates how to access global variables when a local variable with the same name exists. Initially,
two global variables, $day and $month, are defined with the values "Monday" and "January,"
respectively. The calendar function is then defined, within which a local variable $day is assigned the
value "Tuesday." This local variable masks the global $day within the function's scope.

To access the global $month variable within the function, the global keyword is used, making $month
accessible and allowing its value to be printed. Inside the function, print "local day is $day <br />";
outputs "local day is Tuesday," reflecting the value of the local $day.

Next, the global $day is accessed through the $GLOBALS array, a PHP superglobal that contains
references to all global variables. The statement $gday = $GLOBALS['day']; retrieves the value of the
global $day ("Monday") and assigns it to $gday. The line print "global day is $gday <br />"; then prints

Unit: 4 - Implementing Scope and Separate Compilation 10


DCA1209: Principle of Programming Languages

"global day is Monday." Finally, the function prints the global $month using print "global month is
$month <br />";, resulting in "global month is January." When the calendar function is called, the
output is:

The output will be:


local day is Tuesday
global day is Monday
global month is January

In JavaScript, global variables work similarly to PHP but cannot be accessed if there is a local variable
with an identical or the same name that exists in a function. Python's handling of global variables is
unique; variables are declared when assigned values. To change or modify a global variable within a
function, it must be declared as global using the global keyword.

For example:
day = "Monday"
def tester():
global day
print("The global day is:", day)
day = "Tuesday"
print("The new value of day is:", day)
tester()

The function tester is defined to demonstrate how to reference and modify this global variable within
a function. When the tester function is called, it first uses the global keyword to specify that it is
referring to the global variable day. This allows the function to access and modify the global variable
instead of creating a new local variable. The function then prints the current value of day, which is
"Monday". After that, it updates the value of day to "Tuesday" and prints this new value.

The output will be:


The global day is: Monday
The new value of day is: Tuesday

One significant advantage of static scope is predictability. Since the scope of variables is determined
at compile time, it becomes easier to predict and understand their behavior within the program. This
clarity simplifies debugging and maintenance, as developers can readily identify where variables are
declared and how they are used throughout the code. Additionally, static scope helps prevent

Unit: 4 - Implementing Scope and Separate Compilation 11


DCA1209: Principle of Programming Languages

unintended side effects by limiting variable visibility. By confining the scope of variables to specific
blocks or functions, static scoping reduces the likelihood of variable name collisions and unintentional
modifications, enhancing code reliability and stability.

Even with its benefits, static scope has some disadvantages, primarily related to reduced flexibility.
The inability to change variable bindings at runtime can be limiting in scenarios where dynamic
behavior is desired. This restriction can make certain programming tasks more cumbersome and less
efficient, as developers may need to implement additional structures or workarounds to achieve the
desired functionality. In contrast to dynamic scope, static scope's fixed nature at compile time can
sometimes delay the ability to adapt and modify variable bindings based on runtime conditions,
potentially reducing the overall flexibility of the code.

2.5. Dynamic Scope


Dynamic scoping defines the scope of variables by considering the order in which subprograms are
called, rather than their physical or spatial arrangement , i.e., the scope is established at run-time.

function big() {
function sub1() {
var x = 7;
}
function sub2() {
var y = x;
var z = 3;
}
var x = 3;
}

In the above code, the function big which contains two nested functions sub1 and sub2. Within sub1,
a local variable x is declared and initialized to 7. In sub2, another variable y is assigned the value of x,
and z is set to 3. With dynamic scoping, the reference to x in sub2 can refer to either x from sub1 or
the outer x depending on the calling sequence of these functions.

Dynamic scoping works by searching for variable declarations starting from the local scope and
moving up through the calling sequence of functions. Suppose a variable is not found in the local
declarations. In that case, the search continues in the calling function, and then in that function's caller,
and so on, until a declaration is found or a runtime error occurs if none is found.

Unit: 4 - Implementing Scope and Separate Compilation 12


DCA1209: Principle of Programming Languages

Dynamic scope offers significant flexibility by allowing variable bindings to change at runtime,
enabling more dynamic behaviour within programs. This adaptability is particularly useful in
scripting and interpreted languages where modifying variable bindings on the fly can simplify certain
variable access patterns. For example, in environments where functions or blocks of code need to
access variables defined in calling functions, dynamic scope can streamline the process, reducing the
need for complex parameter passing or global variables. This facilitation of use can make
programming more intuitive and efficient in scenarios where the call sequence heavily influences
variable usage.

One major disadvantage is unpredictability, as the binding of variables can change based on the call
sequence, making programs harder to understand and debug. Developers may struggle to track
variable origins and modifications, leading to increased potential for errors and unintended side
effects. Additionally, dynamic scope introduces performance overhead due to the need for runtime
lookups to resolve variable bindings. These lookups can impact the overall performance of the
program, especially in scenarios where variable resolution is frequent or complex. Thus, while
dynamic scope provides flexibility and ease of use, it can complicate program comprehension and
reduce execution efficiency.

Unit: 4 - Implementing Scope and Separate Compilation 13


DCA1209: Principle of Programming Languages

3. BINDING OF REFERENCING ENVIRONMENTS


Scope rules control the variables and subroutines accessible at various points in a program. Static
scope rules determine variable access based on the program's lexical structure, while dynamic scope
rules base access on the runtime order of subroutine calls. This distinction becomes essential when
subroutines are referenced indirectly, such as through parameter passing.

Applying scope rules either when the subroutine reference is created or when it is called can
significantly affect program behavior.

Example: Code to demonstrate the significance of binding rules

type person = record


...
age : integer
...
threshold : integer
people : database
function older than(p : person) : boolean
return p.age ≥ threshold
procedure print person(p : person)
–– Call appropriate I/O routines to print record on standard output.
–– Make use of nonlocal variable line length to format data in columns.
...
procedure print selected records(db : database;
predicate, print routine : procedure)
line length : integer
if device type(stdout) = terminal
line length := 80
else –– Standard output is a file or printer.
line length := 132
foreach record r in db
–– Iterating over these may actually be
–– a lot more complicated than a ‘for’ loop.
if predicate(r)

Unit: 4 - Implementing Scope and Separate Compilation 14


DCA1209: Principle of Programming Languages

print routine(r)
–– main program
...
threshold := 35
print selected records(people, older than, print person)

The above example highlights the significance of binding rules in a hypothetical programming
language, showcasing how static and dynamic scoping impact variable resolution and subroutine
execution. The code involves defining a person record type, a function to check if a person is older
than a specified threshold, and a procedure to print a person's details. A main procedure,
print_selected_records, also takes a database of person records, a predicate function, and a print
routine as parameters. This procedure sets a line length variable based on the output device type and
iterates over the database records, printing those that satisfy the predicate.

In the main program, the threshold is set to 35, and print_selected_records is called with the database,
the older_than predicate, and the print_person routine. Under static scoping, the older_than function
uses the threshold variable defined in the main program, as its binding is determined at compile time
based on the lexical structure of the code. Conversely, with dynamic scoping, the threshold variable
referenced by older_than would depend on the runtime call stack, potentially leading to different
bindings.

The line_length variable, set within print_selected_records and used by print_person, demonstrates
the importance of dynamic scoping for resolving nonlocal references at runtime. Additionally, the
example shows how passing subroutines as parameters can complicate binding, depending on
whether shallow or deep binding is used. Overall, this example emphasizes the crucial role of
understanding binding rules in programming language design and implementation, as they affect
variable visibility, program behavior, and subroutine interactions.

Scope rules determine which variables and subroutines are accessible at various points in a program.
Static scope rules define this access based on the lexical structure of the code—where the code is
written. Dynamic scope rules, however, determine access based on the order in which subroutines
are called during program execution.

Shallow binding delays the creation of the referencing environment for a subroutine until it is
actually called. This approach is typical in languages with dynamic scoping. The
print_selected_records procedure in an Algol-like language illustrates this concept in the given

Unit: 4 - Implementing Scope and Separate Compilation 15


DCA1209: Principle of Programming Languages

example. This procedure traverses records in a database, using a predicate function to decide whether
to print each record and a subroutine (print_routine) to format the output. The print_person
subroutine, used to print records of people, relies on a non-local variable line_length to format its
output correctly. Shallow binding ensures that when print_person is called by print_selected_records,
it can access the line_length variable set by print_selected_records.

Deep binding, on the other hand, establishes the referencing environment for a subroutine when it
is first passed as a parameter, preserving that environment until the subroutine is called. This method
is essential when the referencing environment at the time of the subroutine's creation needs to be
maintained. In the above example, the older_than function is passed to print_selected_records. If
print_selected_records has a local variable named threshold, this could conflict with the global
threshold variable intended to be used by older_than. Deep binding ensures that older_than uses the
global threshold variable, not any local variable of the same name within print_selected_records. This
necessity for deep binding, particularly to solve issues known as the funarg (function argument
problem) problem in Lisp, underscores the importance of selecting appropriate binding strategies to
ensure program reliability and intended behavior.

In languages with dynamic scoping, shallow binding is typically the default, as it allows subroutines
to adapt to the local environments where they are called. However, deep binding is necessary in
situations where maintaining a subroutine's original environment is crucial for its correct operation.

3.1. Subroutine Closures


Deep binding is the process of constructing a clear and specific representation of a referring
environment and combining it with a reference to the subroutine, resulting in the formation of a
closure. A closure often consists of a reference to the code of a function and the context in which it
was created. In programming languages that employ dynamic scoping, the referencing environment
might be symbolised by a top-of-stack pointer. When a subroutine is invoked through a closure, the
primary reference to the referenced environment is temporarily substituted by the saved reference,
enabling new bindings within the subroutine to access and modify existing entries in the main
environment.

For languages that use a central reference table for dynamic scoping, creating a closure involves
copying parts of the table, potentially saving only the relevant names to reduce overhead. Dynamic
scope languages often offer the option of deep binding, like Lisp, where functions can be passed and
executed in their original environment using closures. In statically scoped languages, deep binding is

Unit: 4 - Implementing Scope and Separate Compilation 16


DCA1209: Principle of Programming Languages

typically the default, ensuring subroutines access the correct instances of objects based on their
lexical nesting. This is essential for maintaining the integrity of referencing environments, especially
with recursive calls, as closures capture the current instance of every object at the time of their
creation.

Example: Code demonstrate deep binding in Pascal

program binding_example(input, output);


procedure A(I : integer; procedure P);
procedure B;
begin
writeln(I);
end;
begin (* A *)
if I > 1 then
P
else
A(2, B);
end;
procedure C; begin end;
begin (* main *)
A(1, C);
end.

The above Pascal code demonstrates the concept of deep binding using nested procedures and the
passing of subroutines as parameters. The main program begins by calling procedure A with
parameters 1 and C, where C is an empty procedure serving as a placeholder. Procedure A accepts an
integer I and a procedure P. Within A, there is a nested procedure B, which prints the value of I. In the
main body of A, there is a conditional statement that checks if I is greater than 1. If true, it calls
procedure P; otherwise, it recursively calls itself with parameters 2 and B.

During execution, the initial call A(1, C) starts with I equal to 1 and P set to C. Since I is not greater
than 1, the else branch executes, calling A(2, B). Now I is 2 and P is B. Since I is greater than 1, P (which
is B) is called, and procedure B prints the value of I, which is now 2. This demonstrates deep binding,
as the closure created for P ensures that B retains the value of I from the defining environment (when
I was 2). Thus, the code shows how the environment in which a subroutine is created is preserved

Unit: 4 - Implementing Scope and Separate Compilation 17


DCA1209: Principle of Programming Languages

and used during its execution, ensuring that variables hold the correct values from their defining
scope.

3.2. First- and Second-Class Subroutines


In the field of programming, the term "first-class status" refers to values that have the ability to be
used as parameters, returned from subroutines, or assigned to variables. Typical instances include
integers and characters. Second-class values can be used or passed as parameters in a function, but
they cannot be returned or assigned. On the other hand, third-class values are not even allowed to be
used or passed as arguments.

Most imperative languages treat subroutines as second-class values, but Ada 83 treats them as third-
class. Conversely, functional programming languages, C#, Perl, Python, and some other imperative
languages treat subroutines as first-class values.

First-class subroutines introduce complexity in languages with nested scopes because a subroutine
reference may outlive the scope in which it was declared. For example, in Scheme, a function f created
within a let construct retains access to a variable from its enclosing scope even after that scope has
exited.

Example: Describing Variables in First-Class Subroutines in Scheme

(define plus_x (lambda (x)


(lambda (y) (+ x y))))
...
(let ((f (plus_x 2)))
(f 3)) ; returns 5

This code demonstrates how Scheme handles first-class subroutines and closures:

• First-Class Subroutines: In Scheme, functions (subroutines) can be passed as parameters,


returned from other functions, and given or assigned to variables. This is illustrated by the fact
that plus_x returns a lambda function which is then assigned to f.
• Closures: When plus_x is called with 2, it returns a lambda function that remembers the value
of x at the time of its creation. This lambda function, when assigned to f, forms a closure
capturing the environment in which it was created. Hence, even though plus_x has returned,
the lambda function inside f still has access to the value 2.

Unit: 4 - Implementing Scope and Separate Compilation 18


DCA1209: Principle of Programming Languages

• Capturing Environment: The lambda function (lambda (y) (+ x y)) captures the value of x
from its defining environment. When f is later called with 3, it correctly computes the sum
using the captured x value 2, demonstrating how closures can outlive their defining scope
while still retaining access to their original environment.

Local objects in functional languages usually have unlimited lifetimes, managed by garbage collection.
In Algol-family languages, they have limited lifetimes, being destroyed at the end of their scope.

This difference affects memory allocation: limited extent objects can use stack allocation, while
unlimited extent objects require heap allocation.

To prevent dangling references in closures, imperative languages with first-class subroutines use
various mechanisms.

C, C++, and Fortran lack nested subroutines, while Modula-2 restricts references to outermost
subroutines.

Modula-3 allows nested subroutines as parameters but restricts their return or storage.

Ada 95 uses a containment rule to ensure subroutine references do not outlive their intended scope.

Unit: 4 - Implementing Scope and Separate Compilation 19


DCA1209: Principle of Programming Languages

4. SEPARATE COMPILATION
Separate compilation is a fundamental feature for programming languages that support the
development of large programs. It allows different parts of a program to be compiled independently
and then linked together. This feature is essential for several reasons:

1. Incremental Development: In large programs, incremental development is a common practice


where the program is built and tested piece by piece. Separate compilation enables developers to
compile and test individual modules or components of the program without the need to recompile
the entire codebase. This modular approach significantly accelerates the development process.
For example, if a developer makes changes to a specific module, they can recompile only that
module and test it in isolation. This saves time and reduces the risk of introducing new errors into
other parts of the program. The ability to incrementally build and test components fosters a more
manageable and efficient development workflow, allowing for faster iterations and quicker
feedback.
2. Efficiency: Compiling an entire large program from scratch can be an extremely time-consuming
operation, sometimes taking several hours depending on the size and complexity of the codebase.
Separate compilation mitigates this issue by allowing only the modified parts of the program to
be recompiled. This selective compilation process saves valuable time and computational
resources. For example, in a project with hundreds of source files, if a developer modifies just one
file, only that file and any dependent files need to be recompiled. The rest of the program remains
untouched, thus preserving previously compiled code and speeding up the overall build process.
This efficiency is particularly beneficial in large-scale software projects where frequent code
changes are common.
3. Encapsulation and Modularization: Separate compilation promotes the principles of
encapsulation and modularization, which are fundamental to good software design. Each module
can be developed, tested, and maintained independently by breaking down a program into smaller,
independent modules. This modular approach enhances code manageability and reusability. For
example, a module responsible for handling database operations can be developed separately
from the module managing the user interface. This separation of concerns allows developers to
focus on specific functionalities without being overwhelmed by the entire codebase.
Encapsulation ensures that the internal details of a module are hidden from other parts of the
program, exposing only a well-defined interface. This makes it easier to understand, test, and

Unit: 4 - Implementing Scope and Separate Compilation 20


DCA1209: Principle of Programming Languages

debug each module individually. Moreover, modularization facilitates reusability, as well-


designed modules can be reused across different projects with minimal modifications.

Examples:
i. C Program:
math.h
#ifndef MATH_H
#define MATH_H
int add(int x, int y);
int multiply(int x, int y);
#endif

Math.c
#include "math.h"
int add(int x, int y) {
return x + y;}

int multiply(int x, int y) {


return x * y;
}

Main.c
#include <stdio.h>
#include "math.h"
int main() {
int a = 5, b = 3;
printf("Add: %d\n", add(a, b));
printf("Multiply: %d\n", multiply(a, b));
return 0;
}

The given C program demonstrates the concept of separate compilation. Separate compilation allows
you to split a program into multiple files, which can be compiled independently and then linked
together to create the final executable. This approach improves modularity, maintainability, and
compilation efficiency. The program consists of three files: math.h, math.c, and main.c.

Unit: 4 - Implementing Scope and Separate Compilation 21


DCA1209: Principle of Programming Languages

math.h - Header File

This file contains the declarations of the functions add and multiply. Header files are included in other
files to provide the necessary declarations for the functions they use.

Preprocessor Directives: #ifndef, #define, and #endif are used to prevent multiple inclusions of the
same header file.
Function Declarations: int add(int x, int y); and int multiply(int x, int y); are declarations of the
functions that will be defined in math.c.

math.c - Source File

This file contains the definitions of the functions declared in math.h. It implements the logic for add
and multiply.

Include Header File: #include "math.h" includes the header file to ensure that the function definitions
match their declarations.
Function Definitions: int add(int x, int y) and int multiply(int x, int y) are defined to return the sum
and product of x and y, respectively.

main.c - Main Program File

This file contains the main function that uses the functions add and multiply from math.c.

Include Header Files: #include <stdio.h> includes the standard I/O library, and #include "math.h"
includes the header file with function declarations.
Main Function: The main function declares two integers, a and b, and then prints the results of add(a,
b) and multiply(a, b) using printf

• Separate Compilation: Each source file (math.c and main.c) is compiled independently into object
files (math.o and main.o). These object files are then linked together to create the executable.
• Modularity: By separating the function definitions into math.c and keeping the declarations in
math.h, the code becomes more modular and easier to maintain. Changes to the implementation
in math.c do not require changes in main.c, as long as the interface in math.h remains the same.
• Efficiency: Only the modified files need to be recompiled, reducing the overall compilation time
for large projects.

This approach to programming with separate compilation enhances code organization, reusability,
and collaboration among multiple developers working on different parts of a project.

Unit: 4 - Implementing Scope and Separate Compilation 22


DCA1209: Principle of Programming Languages

ii. C++ Program


Math.hpp
#ifndef MATH_HPP
#define MATH_HPP
int add(int x, int y);
int multiply(int x, int y);
#endif

math.cpp
#include "math.hpp"
int add(int x, int y) {
return x + y;
}
int multiply(int x, int y) {
return x * y;
}

main.cpp
#include <iostream>
#include "math.hpp"
int main() {
int a = 5, b = 3;
std::cout << "Add: " << add(a, b) << std::endl;
std::cout << "Multiply: " << multiply(a, b) << std::endl;
return 0;
}

The given C++ code demonstrates the concept of separate compilation. Separate compilation allows
different parts of a program to be compiled independently and then linked together to form the final
executable. This approach is beneficial for large projects, as it improves modularity, maintainability,
and compilation efficiency. The program consists of three files: math.hpp, math.cpp, and main.cpp.

math.hpp - Header File

This file contains the declarations of the functions add and multiply. Header files are included in other
files to provide the necessary declarations for the functions they use.

Unit: 4 - Implementing Scope and Separate Compilation 23


DCA1209: Principle of Programming Languages

Include Guard: #ifndef MATH_HPP, #define MATH_HPP, and #endif are preprocessor directives that
prevent multiple inclusions of the same header file, which can lead to redefinition errors.
Function Declarations: int add(int x, int y); and int multiply(int x, int y); are declarations of the
functions that will be defined in math.cpp.

math.cpp - Source File

This file contains the definitions of the functions declared in math.hpp. It implements the logic for add
and multiply.

Include Header File: #include "math.hpp" includes the header file to ensure that the function
definitions match their declarations.
Function Definitions: int add(int x, int y) and int multiply(int x, int y) are defined to return the sum
and product of x and y, respectively.

main.cpp - Main Program File

This file contains the main function that uses the functions add and multiply from math.cpp.

Include Header Files: #include <iostream> includes the standard I/O library, and #include "math.hpp"
includes the header file with function declarations.
Main Function: The main function declares two integers, a and b, and then prints the results of add(a,
b) and multiply(a, b) using std::cout.

Separate Compilation: Each source file (math.cpp and main.cpp) is compiled independently into
object files (math.o and main.o). These object files are then linked together to create the executable.

Modularity: By separating the function definitions into math.cpp and keeping the declarations in
math.hpp, the code becomes more modular and easier to maintain. Changes to the implementation in
math.cpp do not require changes in main.cpp, as long as the interface in math.hpp remains the same.

Efficiency: Only the modified files need to be recompiled, reducing the overall compilation time for
large projects.

iii. Java Program:


MathOperations.java
public class MathOperations {
public static int add(int x, int y) {
return x + y;
}

Unit: 4 - Implementing Scope and Separate Compilation 24


DCA1209: Principle of Programming Languages

public static int multiply(int x, int y) {


return x * y;
}
}

MainApp.java
public class MainApp {
public static void main(String[] args) {
int a = 5, b = 3;
System.out.println("Add: " + MathOperations.add(a, b));
System.out.println("Multiply: " + MathOperations.multiply(a, b));
}
}

In Java, separate compilation is achieved through the use of classes, which are compiled into
separate .class files. Each class is typically defined in its own .java file, making it easier to manage
large projects. Below is an example demonstrating separate compilation using two classes:
MathOperations and MainApp.

MathOperations.java

This file defines the MathOperations class, which contains two static methods: add and multiply.

Class Definition: The MathOperations class is defined with two static methods.
Method Definitions: The add method returns the sum of x and y, and the multiply method returns their
product.

MainApp.java

This file defines the MainApp class, which contains the main method that uses the MathOperations
class.

Class Definition: The MainApp class is defined with a main method.


Using MathOperations: The main method calls the add and multiply methods of the MathOperations
class and prints the results.

Separate Compilation: Each .java file is compiled independently into a .class file.
MathOperations.java compiles to MathOperations.class, and MainApp.java compiles to MainApp.class.

Unit: 4 - Implementing Scope and Separate Compilation 25


DCA1209: Principle of Programming Languages

Modularity: The MathOperations class can be used by any other class in the project, promoting code
reuse. The MainApp class uses the methods defined in MathOperations, demonstrating how separate
compilation supports modularity.

Encapsulation: The methods in MathOperations are encapsulated within the class. This separation
allows for better organization and maintainability of the code. Changes to the implementation of
MathOperations do not require changes in MainApp, as long as the interface (method signatures)
remains the same.

Unit: 4 - Implementing Scope and Separate Compilation 26


DCA1209: Principle of Programming Languages

5. SUMMARY
In this unit, learners have studied the foundational concepts of scope rules, binding environments,
and separate compilation, which are essential for mastering programming language principles and
improving code quality and also Scope Rules, which are critical for understanding the visibility and
lifetime of variables and functions within a program.

Static Scoping determines the scope of a variable based on the program’s structure, making it easier
to predict and understand variable behavior. This concept is contrasted with Dynamic Scoping, where
the scope of variables is determined at runtime based on the calling sequence of functions, offering
more flexibility but often leading to less predictable behavior. Learners also examined the use of
Blocks, which create new scopes within a program, allowing for the declaration of local variables and
enhancing modularity.

The Declaration Order was another important topic, highlighting how the sequence of variable and
function declarations affects their accessibility and use within a program. Global Scope was discussed
as the scope where variables and functions are accessible throughout the entire program, providing
a means for shared data across different parts of the program.

In the section on Binding of Referencing Environments, learners explored Subroutine Closures, which
capture the environment in which a function is defined, preserving variable bindings for later use.
This concept is particularly important in functional programming languages where functions can be
passed as first-class citizens. Understanding the difference between First- and Second-Class
Subroutines is crucial, as it distinguishes between subroutines that can be passed as arguments or
returned from other subroutines (first-class) and those that cannot (second-class).

Finally, the unit covered Separate Compilation, a technique used to manage large programs by
dividing them into smaller, independently compiled modules. This approach enhances code
modularity, maintainability, and allows for parallel development by different team members.

Unit: 4 - Implementing Scope and Separate Compilation 27


DCA1209: Principle of Programming Languages

6. SELF-ASSESSMENT QUESTIONS
Multiple choice Questions
1 Which of the following determines the scope of a variable based on the program’s structure?
A) Dynamic Scoping
B) Static Scoping
C) Block Scoping
D) Global Scoping
2 Which of the following allows the declaration of local variables within a new scope?
A) Global Scope
B) Dynamic Scope
C) Blocks
D) Closures
3 Which scope allows variables and functions to be accessible throughout the entire program?
A) Local Scope
B) Block Scope
C) Global Scope
D) Dynamic Scope
4 What captures the environment in which a function is defined, preserving variable bindings
for later use?
A) Blocks
B) Closures
C) Global Scope
D) Static Scope
5 Which of the following can be passed as arguments or returned from other subroutines?
A) First-Class Subroutines
B) Second-Class Subroutines
C) Third-Class Subroutines
D) Dynamic Subroutines
6 Which programming technique enhances code modularity and maintainability by dividing
programs into smaller units?
A) Static Scoping

Unit: 4 - Implementing Scope and Separate Compilation 28


DCA1209: Principle of Programming Languages

B) Dynamic Scoping
C) Separate Compilation
D) Subroutine Closures
7 Which of the following is a benefit of static scoping?
A) Increased flexibility
B) Easier prediction of variable behavior
C) Runtime determination of scope
D) Improved memory usage
Fill in the Blanks:

8 Separate compilation divides programs into smaller, ______ compiled modules.

9 First-class subroutines can be passed as arguments or ______ from other subroutines.

True or False:

10 Dynamic scoping is easier to predict than static scoping.

11 Closures capture the environment in which a function is defined.

12 Separate compilation helps manage large programs by dividing them into smaller units.

Unit: 4 - Implementing Scope and Separate Compilation 29


DCA1209: Principle of Programming Languages

7. GLOSSARY
Financial Management is concerned with the procurement of the least cost funds, and its effective

Scope - The region of a program where a binding of a name to an entity is valid.

A scoping rule where the scope of a variable is determined at compile time


Static Scoping -
based on the program text.

Sections of code that create a new scope, typically enclosed in curly braces {}
Blocks -
in many programming languages.

Declaration The sequence in which variables and functions are declared, which can affect
-
Order their scope and visibility.

The outermost scope in a program, where variables and functions are


Global Scope -
accessible from anywhere in the code.

A scoping rule where the scope of a variable is determined at runtime based


Dynamic Scope -
on the calling sequence of subroutines.

Binding - The association between a name and an entity, such as a variable or a function.

Referencing
- The set of active bindings at a given point in the execution of a program.
Environment

Subroutine Function objects that encapsulate both the function's code and the referencing
-
Closures environment in which it was defined.

First-Class Functions that can be passed as arguments, returned from other functions,
-
Subroutines and assigned to variables.

Second-Class Functions that can be passed as arguments but cannot be returned from other
-
Subroutines functions or assigned to variables.

Separate The process of compiling program modules independently and then linking
-
Compilation them together to form an executable.

Module - A self-contained unit of code that can be separately compiled and linked.

Unit: 4 - Implementing Scope and Separate Compilation 30


DCA1209: Principle of Programming Languages

A file containing declarations and macro definitions to be shared between


Header File -
several source files.

The output of a compiler, containing machine code and data that can be linked
Object File -
to form an executable.

Unit: 4 - Implementing Scope and Separate Compilation 31


DCA1209: Principle of Programming Languages

8. TERMINAL QUESTIONS
1. Define static scoping and explain its significance in programming.
2. What are blocks in programming languages and how do they affect scope?
3. Explain the importance of declaration order in a statically scoped language.
4. How does global scope differ from local scope in programming?
5. Describe dynamic scoping with an example.
6. Illustrate the concept of referencing environments and their significance in subroutine binding.
7. Explain subroutine closures with a detailed example in a programming language of your choice.
8. Discuss the differences between first-class and second-class subroutines and their implications.
9. Describe the process of separate compilation and its benefits for large-scale software
development.
10. Explain how separate compilation is handled in C++ with appropriate examples.

Unit: 4 - Implementing Scope and Separate Compilation 32


DCA1209: Principle of Programming Languages

9. ANSWERS
Self-Assessment Questions
1. Static Scoping
2. Blocks
3. Global Scope
4. Closures
5. First-Class Subroutines
6. Separate Compilation
7. Easier prediction of variable behaviour
8. Independently
9. Returned
10. False
11. True
12. True

Terminal Questions Answers


1. Refer to section 2.1
2. Refer to section 2.2
3. Refer to section 2.3
4. Refer to section 2.4
5. Refer to section 2.5
6. Refer to section 3
7. Refer to section 3.1
8. Refer to section 3.2
9. Refer to section 4
10. Refer to section 4

Unit: 4 - Implementing Scope and Separate Compilation 33


DCA1209: Principle of Programming Languages

10. REFERENCES
1. Michael L.Scott, “Programming Language Pragmatics”, 2015, Fourth Edition, Morgan Kaufmann
publisher.
2. Robert W. Sebesta, “Concepts of Programming Languages”, 2012, Tenth Edition, . ISBN 10: 0-13-
139531-9.

Unit: 4 - Implementing Scope and Separate Compilation 34


DCA1209: Principle of Programming Languages

BACHELOR OF COMPUTER APPLICATIONS


SEMESTER 2

DCA1209
PRINCIPLE OF PROGRAMMING
LANGUAGES
Unit: 5 - Control Flow: Structured and Unstructured 1
DCA1209: Principle of Programming Languages

Unit – 5
Control Flow: Structured and
Unstructured

Unit: 5 - Control Flow: Structured and Unstructured 2


DCA1209: Principle of Programming Languages

TABLE OF CONTENTS

Fig No /
SL SAQ / Page
Topic Table /
No Activity No
Graph
1 Introduction - -
4-5
1.1 Objectives - -
2 Expression Components - -

2.1 Precedence and Associativity - - 6 - 16

2.2 Assignments - -
3 Structured and Unstructured Flow - -
17 - 20
3.1 Continuations - -
4 Sequencing - - 21 - 22
5 Selection - -

5.1 Short-Circuited Conditions - - 23 - 30

5.2 Case/Switch Statements - -


6 Summary - - 31
7 Self-Assessment Questions - 1 32 - 33
8 Glossary - - 35
9 Terminal Questions - - 36
10 Answers - - 37
11 References - - 38

Unit: 5 - Control Flow: Structured and Unstructured 3


DCA1209: Principle of Programming Languages

1. INTRODUCTION
In the previous unit 4, learners have studied how variables and subroutines are accessed within a
program, focusing on scope rules. They learned about static scoping, where variable access is
determined by the structure of the code, and dynamic scoping, which depends on the order of function
calls during execution. The unit also discussed the importance of blocks and declaration order in
controlling variable visibility, as well as the global scope that allows variables to be accessed
throughout the program. Additionally, learners were introduced to subroutine closures and the
difference between first- and second-class subroutines, along with the concept of separate
compilation, which enables parts of a program to be compiled independently.

In this unit, learners will study the fundamental aspects of programming languages that dictate how
expressions are formed, evaluated, and controlled within a program. The unit begins with an
exploration of Expression Components, focusing on the concepts of Precedence and Associativity
which determine the order in which operators are applied in expressions. The learners will explore
the topic of Assignments, where learners will explore into how values are stored and manipulated
within variables.

Then learners examine the Structured and Unstructured Flow, providing insights into the various
ways control flow can be managed in a program, including the use of traditional constructs like loops
and conditionals, as well as the less conventional use of unstructured flow mechanisms.

Next, learners will explore Sequencing, which is central to imperative programming, governing the
order in which instructions are executed. This is followed by an in-depth look at Selection
mechanisms, with particular attention given to Short-Circuited Conditions—a technique used to
optimize the evaluation of Boolean expressions—and the implementation of Case/Switch Statements,
which offer an efficient and readable alternative to multiple nested conditions.

To study this unit, learners have to focus on understanding the logic behind operator precedence and
control flow constructs by practicing with real code examples. Use visual aids like flowcharts to grasp
the concepts of structured and unstructured flow. Finally, learners have to reinforce their learning by
solving problems that involve complex expressions and selection statements, ensuring you apply the
concepts in varied contexts.

Unit: 5 - Control Flow: Structured and Unstructured 4


DCA1209: Principle of Programming Languages

1.1. Objectives
After studying this unit, you should be able to:

• Define key terms such as precedence,


associativity, assignments, sequencing, and
selection statements.
• Describe the differences between structured
and unstructured flow in programming.
• Apply the rules of precedence and associativity
to evaluate complex expressions in
programming languages.
• Implement basic and complex control flow structures in code, such as if-else conditions,
loops, and switch statements.

Unit: 5 - Control Flow: Structured and Unstructured 5


DCA1209: Principle of Programming Languages

2. EXPRESSION COMPONENTS
An expression typically consists of either a simple object like a literal constant or a named
variable/constant, or an operator/function applied to a collection of operands or arguments.

The term "operator" is generally used for built-in functions that use special, simple syntax.

The term "operand" refers to the arguments or values that the operator acts upon.

Operator and Function Notations:


• Algol-family Languages: These languages, like many others, allow function calls in the form
of my_func(A, B, C) where A, B, and C are arguments.
• Infix Notation: Common in many imperative languages, it places the operator between the
operands, e.g., a + b.
• Prefix Notation (Cambridge Polish): Used in languages like Lisp, where the operator comes
before the operands, e.g., (+ 1 2).
• Mixfix Notation in Smalltalk: Smalltalk uses infix notation for all functions (messages),
whether built-in or user-defined. The example shows a function call in Smalltalk that would be
infix in other languages.

Conditional Expressions:
• Multi-word infix notation is used in some languages like Algol for conditional expressions, e.g.,
a := if b <> 0 then a/b else 0;.
• In C, the equivalent would be a = b != 0 ? a/b : 0;, which is a three-operand infix operator.

Postfix Notation:
• Used in languages like Postscript, Forth, and some assembly languages, postfix notation places
the operator after the operands, e.g., 1 2 + would be the postfix notation for 1 + 2.
• Examples include the pointer dereferencing operator ^ in Pascal and the post-increment and
post-decrement operators ++ and -- in C and its descendants.

2.1. Precedence and Associativity


Most programming languages include a set of built-in arithmetic and logical operators. When written
in infix notation (i.e., the operator is placed between the operands), without parentheses, these
operators can create ambiguity regarding which operations should be performed first.

Unit: 5 - Control Flow: Structured and Unstructured 6


DCA1209: Principle of Programming Languages

The example provided involves Fortran's exponentiation operator (**), showing different ways to
group an expression like a + b * c**d**e/f. The correct grouping according to Fortran is the last one
shown, which demonstrates the significance of operator precedence in determining the order of
operations.

Role of Precedence and Associativity:


Precedence rules determine how tightly operators bind to their operands, with higher precedence
operators being evaluated first. Associativity rules dictate the order in which operators of the same
precedence level are evaluated, either from left to right or right to left.

These rules are crucial in resolving ambiguities in expressions, ensuring that the intended operations
are performed in the correct order.

Differences Across Languages:


The precedence structure can vary widely across different programming languages. Figure 1
compares operator precedence levels in Fortran, Pascal, C, and Ada, highlighting how different
languages treat the same operators differently.

For example, in C and its descendants, the precedence structure is richer, incorporating various
operators such as type casts, array subscripting, and function calls, which may not be considered in
other languages.

Common Pitfalls and Advice:


The text also mentions some common pitfalls, such as the relatively flat precedence hierarchy in
Pascal, which can lead to mistakes by novice programmers. The advice given is that programmers
who work across different languages should be cautious and use parentheses liberally to avoid
unintended behaviour due to differences in precedence and associativity rules.

Specific Language Considerations:


The examples illustrate how different languages handle these rules. For instance, in Fortran, the
exponentiation operator associates from right to left, which may not be intuitive to all programmers.

The importance of understanding these concepts is emphasized, especially for programmers


transitioning between languages with different operator precedence and associativity rules.

Unit: 5 - Control Flow: Structured and Unstructured 7


DCA1209: Principle of Programming Languages

Figure 1: Precedence levels of Operators in the Languages like Fortran, Pascal, C and Ada.

2.2. Assignments
In purely functional languages, the building blocks of programs are expressions, and the computation
is entirely based on evaluating these expressions. The result of any expression in such languages is
determined solely by the expression itself and its surrounding context. Functional languages often
use recursion to handle complex computations, potentially creating an infinite number of values and
contexts.

In contrast, imperative languages focus on sequences of commands that change the values stored in
variables. Assignments are the primary method for making these changes. Each assignment operation
involves providing a value and specifying a variable where this value should be stored.

A side effect occurs when a programming language construct affects subsequent computation beyond
just returning a value. Purely functional languages avoid side effects; they are designed so that the

Unit: 5 - Control Flow: Structured and Unstructured 8


DCA1209: Principle of Programming Languages

value of an expression depends only on the context in which it is evaluated, not on the timing of that
evaluation. This characteristic is known as referential transparency. In imperative programming,
computations often produce side effects, especially through assignments. Even though an assignment
evaluates to a value, its primary purpose is to change the state of a variable. This change influences
future computations in the program, as the altered variable might be used in subsequent operations.

i. References and Values


• Purely Functional Languages: In purely functional languages, such as Haskell, the concept of
assignments, as seen in imperative languages, does not exist. Instead, these languages rely
entirely on expression evaluation. The output of any computation is derived solely from
evaluating expressions, and these expressions do not have side effects—meaning they do not
alter the program's state. This characteristic is known as referential transparency. For
example, if an expression yields a certain value at one point in time, it will yield the same value
at any other point in time within the same context.

• Imperative Languages: Imperative languages like C, Java, and Pascal rely heavily on
assignments to perform computation. An assignment involves updating the value of a variable
by associating it with a new value. Each assignment has a side effect because it changes the
program's state by altering the contents of a variable's memory location.

For example: a = 8; // assigns the value 5 to the variable a

Here, a is an L-value, and 8 is an R-value.

• L-values and R-values


L-values: L-values represent memory locations that can store values. These are typically
variables that appear on the left-hand side of an assignment.

For example: in the assignment: d = a;


Here, ‘d’ is an L-value because it refers to a memory location that can hold a value of ‘a’.

R-values: R-values are the values assigned to L-values. They can appear on the right-hand
side of an assignment. In the previous example, a is an R-value because it provides the value
that will be stored in the location referred to by ‘d’.

For different programming languages:


Examples from C Programming:
i. a = b + c;

Unit: 5 - Control Flow: Structured and Unstructured 9


DCA1209: Principle of Programming Languages

Here, a is an L-value because it refers to the memory location that will hold the result of b
+ c. The expression b + c is an R-value because it is a computed value that can be assigned
to a.

ii. (f(a) + 3) -> b[c] = 2;


In this C example, f(a) returns a pointer to some element of an array of structures. The
expression b[c] refers to an element within the array pointed to by f(a). The assignment
places the value 2 in the specified location.

Examples from C++ Programming:


L-values in C++: In C++, the concept of L-values is extended.

For example, C++ allows functions to return references (L-values), not just values (R-values):
g(a).b[c] = 2;

In this expression, g(a) returns a reference to a structure. The reference allows direct
assignment to the structure's member b[c].

• Value Model vs. Reference Model


Value Model: In a value model like Pascal, variables directly contain values. For example, if a
and b are variables holding integers:
a = 4;
b = 2;

Each variable has an independent value. Assigning a = b would copy the value of b into a, but
the two variables remain independent.

Reference Model: In the reference model, used in languages like Clu or Java, variables hold
references to values rather than the values themselves. This means that multiple variables can
refer to the same underlying object in memory.

For instance:
b := 2;
c := b;
a := b + c;

Under the reference model, b and c could refer to the exact memory location. Changing b would
automatically change c because they are references to the same value.

Unit: 5 - Control Flow: Structured and Unstructured 10


DCA1209: Principle of Programming Languages

Figure 2: The reference (right) and value (left) forms of a variable's model.

ii. Boxing
In Java Boxing, there is a limitation to passing built-in types uniformly to methods that expect
class-typed parameters when utilising a value model. For instance, while using earlier Java
versions, it was necessary to "wrap" objects of built-in types in corresponding predefined class
types before inserting them into standard container (collection) classes.

Integer N = new Integer(13);


ht.put(N, new Integer(31)); // Integer is a "wrapper" class
Integer M = (Integer) ht.get(N);
int m = M.intValue();

In this code, the integer value 13 is manually wrapped into an Integer object using new
Integer(13). This object can then be stored in the HashTable or other collections that require
objects rather than primitive types. The reverse process, known as unboxing, converts the Integer
object back into a primitive int.

With the release of Java 5, automatic boxing and unboxing were introduced. The compiler now
automatically wraps primitives into their respective object types and vice versa, removing the
need for manual wrapping.

For example:

ht.put(13, 31);
int m = (Integer) ht.get(13);

In this example, 13 and 31 are automatically boxed into Integer objects when added to the
HashTable. Unboxing occurs when the value from the HashTable is retrieved and assigned to
variable m.

In C# Boxing, C# takes boxing a step further by allowing not just the arguments but also the cast
to be "boxed." This eliminates the need for manual conversion in most cases.

Unit: 5 - Control Flow: Structured and Unstructured 11


DCA1209: Principle of Programming Languages

For example:

ht[13] = 31;
int m = (int) ht[13];

In this C# code snippet, ht[13] = 31; uses boxing implicitly to store the integer 31 as an object. The
second line retrieves the value, unboxing it back into an integer.

C# also supports indexers, which allow objects to be accessed using array-like syntax (ht[13]).
This is a powerful feature that allows for more intuitive manipulation of objects that behave like
arrays.

iii. Orthogonality
One of the main design principles of Algol 68 was orthogonality, which aimed to make the features
of the language consistent and combinable in any meaningful way. This means that different
features of the language work together seamlessly, much like orthogonal vectors in linear algebra
that combine without dependency.

Algol 68 stands out for its expression-oriented nature, lacking a separate notion of statements.
Expressions and constructs, which would typically be statements in other languages, can appear
in contexts where expressions are expected.

For instance, the following Algol 68 code is valid:

begin
a := if b < c then d else e;
a := begin f(b); g(c) end;
g(d);
2+3
end

Here, the value of if b < c then d else e depends on the condition and is assigned to a. The begin ...
end block returns the value of the last expression within it. The whole block sums 2 + 3 to get the
value 5.

C language adopts a middle ground, distinguishing between statements and expressions but
allowing expression statements where the value of an expression is computed and discarded. This
allows expressions in places where other languages might restrict to statements.

Unit: 5 - Control Flow: Structured and Unstructured 12


DCA1209: Principle of Programming Languages

Algol 68 and C allow assignments within expressions, where the value of an assignment statement
is its right-hand side value.

In C, the following constructs are valid:

if (a == b) {
/* do the following if a equals b */
}

if (a = b) {
/* assign b into a and then do
the following if the result is nonzero */
}

The first construct is a proper equality check (==), while the second is an assignment (=) that
evaluates to true if b is non-zero. This can lead to bugs, especially for programmers coming from
languages like Ada, where = is used for equality.

C lacks a separate Boolean type, treating zero as false and any non-zero value as true. C99
introduced a _Bool type, but it's essentially an integer. This makes the second form valid but
potentially erroneous if the programmer intended to check equality.

In contrast, C++ introduced a true Boolean type (bool), although it still shares this problem with
C. Java and C# eliminate this issue by enforcing Boolean expressions in conditional statements,
generating compile-time errors for non-Boolean assignments in conditions.

iv. Combination Assignment Operators


Imperative programs frequently need to update variables, which often involves side effects.
Commonly, these updates involve expressions where a variable is reassigned to a new value that
depends on its current value.

For example:
• a = a + 1;
• b.c[3].d = b.c[3].d * e;

These expressions can be heavy to write and read, as they involve repeating the variable on both
sides of the assignment. Moreover, such redundancy can lead to inefficiencies, especially if
calculating the variable's address has side effects.

Unit: 5 - Control Flow: Structured and Unstructured 13


DCA1209: Principle of Programming Languages

If a function or operation used to determine the address of a variable has side effects, the address
calculation should not be repeated.

For instance, consider the C function:

void update(int A[], int index_fn(int n)) {


int i, j;
j = index_fn(i);
A[j] = A[j] + 1;
}

Here, index_fn(i) is called once to avoid repeated address calculations, which might have side
effects or cause inefficiencies if called multiple times.

To simplify the code and avoid such issues, many languages provide assignment operators to
update variables more succinctly. These include:

• a += 1; (equivalent to a = a + 1;)
• b.c[3].d *= e; (equivalent to b.c[3].d = b.c[3].d * e;)

These operators not only make the code more readable but also ensure that address calculations
are performed only once, preventing redundant operations.

C and its descendants also provide prefix (++x, --x) and postfix (x++, x--) increment and decrement
operators, which allow further simplification of code that updates variables based on their current
values.

For example:
A[index_fn(i)]++; (or)
++A[index_fn(i)];

These forms are aesthetically pleasing and efficient, combining the update with the address
calculation in a single operation.

The prefix forms of ++ and -- can be seen as syntactic sugar for the more verbose += and -=
operators. However, the postfix forms are not merely syntactic sugar—they provide a distinct
operation that is more complex to replicate with other operators.

For example, consider:


A[i--] = b;

Unit: 5 - Control Flow: Structured and Unstructured 14


DCA1209: Principle of Programming Languages

To achieve the same result without using postfix, one would need additional variables and code,
making the postfix operators convenient and crucial for writing efficient code.

The discussion also touches on how these operators interact with pointers in C. When applied to
pointers, these operators adjust the pointer's position in memory by the appropriate amount,
depending on the size of the data type it points to. For instance:

*(t = p, p += 1, t) = *(t = q, q += 1, t);

This expression simultaneously increments pointers p and q and assigns the values they point to
before the increment.

v. Multiway Assignment
In many programming languages, such as Clu, ML, Perl, Python, and Ruby, multiway assignment
enables a programmer to assign values to multiple variables in a single expression. This is
particularly useful when you want to swap values or return multiple values from a function
without needing auxiliary variables.

Example: Simple Multiway Assignment


Consider the following multiway assignment:

a, b := c, d;

Here, the comma (,) on the right-hand side is not the C-style sequencing operator but instead
represents a tuple of multiple r-values (right-hand values). The comma on the left-hand side
similarly represents a tuple of l-values (left-hand values). The effect of this assignment is to copy
the value of c into a and the value of d into b.

While this could be achieved using separate assignments like:

a := c;
b := d;

the multiway assignment allows for a more concise and elegant solution.

Example: Advantages of Multiway Assignment


Multiway assignments also provide the ability to perform operations that would otherwise
require additional variables. For example, the following expression:

a, b := b, a;

Unit: 5 - Control Flow: Structured and Unstructured 15


DCA1209: Principle of Programming Languages

swaps the values of a and b without needing a temporary variable. This is a common pattern in
many programming languages, facilitating easier and more readable code for such operations.

Additionally, multiway assignments allow functions to return tuples, enabling the assignment of
multiple return values to variables in a single statement.

For instance:

a, b, c := foo(d, e, f);

This assigns the values returned by the function foo to the variables a, b, and c, all in one operation.

Significance of Multiway Assignments


Multiway assignment eliminates asymmetry (nonorthogonality) in programming languages that
typically allow multiple arguments in a function but only one return value. This feature introduces
more flexibility, enabling the return and assignment of multiple values in a structured and concise
way.

In languages like ML, the concept of multiway assignment is further generalized into a powerful
pattern-matching mechanism, which allows more complex and meaningful decompositions of
data structures during assignment. This mechanism enhances the capability of handling data
structures and extracting values within the programming language, promoting more expressive
and compact code.

Unit: 5 - Control Flow: Structured and Unstructured 16


DCA1209: Principle of Programming Languages

3. STRUCTURED AND UNSTRUCTURED FLOW


Structured programming encourages the use of clear control flow structures like loops and
conditionals instead of arbitrary jumps in the code. It organizes code into blocks or structures that
are easier to follow and debug.

Unstructured Flow Using goto:


Early languages like Fortran used goto statements for control flow, which could lead to "spaghetti
code"—code that is difficult to read and maintain because it jumps around unpredictably.

Example: Control flow with goto in Fortran


if A .lt. B goto 10 ! ".lt." means "<"
...
10

Here, goto is used to jump to label 10 if A is less than B. The label 10 is a statement label that marks
the point in the code to jump to.

Structured Alternatives to goto:


As structured programming evolved, alternatives to goto were developed, such as if...then...else, for,
and while loops, which allow more readable and maintainable control flow.

Example: Leaving the middle of a loop (Pascal)


while not eof do begin
readln(line);
if all_blanks(line) then goto 100;
consume_line(line)
end;
100:

This example shows how goto is used to exit a loop prematurely when a condition (all_blanks(line))
is met.

Example: Returning from the middle of a subroutine


procedure consume_line(var line: string);
...
if line[i] = '%' then goto 100;

Unit: 5 - Control Flow: Structured and Unstructured 17


DCA1209: Principle of Programming Languages

(* rest of line is a comment *)


...
100:
End;

The goto here skips the rest of the subroutine if a specific condition is met, bypassing the remainder
of the logic.

Example: Escaping a nested subroutine


function search(key: string): string;
var rtn: string;
...
procedure search_file(fname: string);
...
if found(key, line) then begin
rtn := line;
goto 100;
end;
...
100: return rtn;
end;

This example shows how goto is used to return a value from deep within nested subroutines by
escaping the nested context when a condition (found(key, line)) is met.

Example: Structured nonlocal transfers


function searchFile(fname, pattern: string): string;
begin
file := File.open(fname);
file.each {|line|
throw :found, line if line =~ /#{pattern}/
}
end
match = catch :found do
searchFile("f1", key)
searchFile("f2", key)

Unit: 5 - Control Flow: Structured and Unstructured 18


DCA1209: Principle of Programming Languages

searchFile("f3", key)
"not found\n"
end
print match

Here, throw is used in Ruby to exit from the normal control flow when a pattern is found in a file. The
catch block handles the thrown exception and allows for a structured exit with a value.

Example: Error-checking with status codes


status := my_proc(args);
if status = ok then ...

This demonstrates a more basic form of error handling using status codes, where the function
my_proc returns a status that is checked before proceeding.

3.1. Continuations
Continuations refer to a feature in some programming languages that allows a program to capture its
current execution state (context) and later restore it, essentially "pausing" and "resuming" the
execution at a particular point.

This powerful tool in functional programming is fundamental to denotational semantics.

• Low-Level Perspective: At a low level, a continuation is essentially a pointer to a code


location and a referencing environment, which can be used to jump back to a specific point in
the program. This is similar to how a goto statement works but with the added ability to
restore the context in which the jump was made.
• High-Level Perspective: At a higher level, a continuation is an abstraction that captures a
point in executing a program where the program can continue later. They are "first-class
values" in languages like Scheme and Ruby, which can be passed around and manipulated
within the program, just like other data types.
• Scheme's Continuation Support: In Scheme, continuations are supported through a function
called call-with-current-continuation (often abbreviated as call/cc). This function takes
another function f as an argument and passes to f a continuation c. This continuation c captures
the current execution state, allowing f to "pause" the program and then "resume" it from that
exact state at any later time.

Unit: 5 - Control Flow: Structured and Unstructured 19


DCA1209: Principle of Programming Languages

Design and Implementation Considerations


The implementation of continuations in Scheme and Ruby is relatively straightforward due to the way
these languages handle local variables and activation records:

• Local Variables: Since local variables in these languages have unlimited extent, they are
typically allocated on the heap (not the stack). This means when a continuation is invoked, the
local variables and the execution state are already available without needing explicit
deallocation or restoration.
• Garbage Collection: When jumping through a continuation, frames that are no longer needed
can be reclaimed by garbage collection, making continuations memory efficient.
• Activation Records: Since the continuation closure retains everything required to resume the
execution, no manual state restoration (like saved registers) is necessary.

Functional Details of call/cc


• The call/cc function takes a single function f as its argument. It passes f a continuation c that
encapsulates the current execution state. At any point, f can invoke c to return to that exact
point in the program.
• Nested Calls: If nested calls exist, c can unwind the stack, popping out of multiple calls just
like exceptions do in other programming languages.
• Saving and Reusing Continuations: Continuations in Scheme can be stored in variables,
passed around, or even returned from functions. This allows for complex control flows, like
simulating exceptions, coroutines, multilevel returns, etc.

While continuations are extremely powerful and versatile, enabling the creation of complex control
structures like iterators, exceptions, and more, they can also lead to highly complex and difficult-to-
understand code if not used carefully. Their flexibility can allow programmers to create inscrutable
logic that is hard to debug or maintain.

Unit: 5 - Control Flow: Structured and Unstructured 20


DCA1209: Principle of Programming Languages

4. SEQUENCING
Sequencing is a fundamental concept in imperative programming, determining the order in which
side effects, such as variable assignments, occur. In imperative languages, a statement typically
follows another, executing in the sequence they are written. This order of execution is crucial because
it directly affects the program's state.

A compound statement, often enclosed within begin...end in languages like Algol 68 or {...} in C, groups
multiple statements to be executed as a unit. Such grouping is known as a block. The value of a
compound statement in these languages is typically the value of its final element, making sequencing
not just about order but also about value production.

In languages like Common Lisp, the value returned by a sequence of expressions can be the first,
second, or last expression's value, depending on the context. This is unique compared to purely
imperative languages, where sequencing is more rigid.

The side effects revolve around the impact of certain operations on the state of a program, particularly
within functions. Side-effect freedom refers to functions that produce the same output given the
same input, without modifying any external state. Such functions are idempotent, meaning repeated
calls with the same arguments will yield the same result without altering the program state.

However, not all functions can or should be side-effect-free. For instance, a side effect is essential in a
pseudo-random number generator because the function must return a different value each time it is
called. This necessitates changing the internal state (e.g., updating a seed value).

For example:

Where,
• srand(seed : integer) initialises a random number generator with a seed, ensuring that the
sequence of random numbers is reproducible.
• rand(): integer generates a new random number each time it is called, which inherently
requires a side effect to produce varying outputs.

Unit: 5 - Control Flow: Structured and Unstructured 21


DCA1209: Principle of Programming Languages

Ada, a programming language known for its robustness, offers a compromise. While it allows
functions to have side effects, such as changing static or global variables, it prohibits a function from
directly modifying its parameters. This restriction ensures a level of predictability and safety,
particularly in complex systems where unintentional parameter modification could lead to bugs or
unpredictable behaviour.

Unit: 5 - Control Flow: Structured and Unstructured 22


DCA1209: Principle of Programming Languages

5. SELECTION
The selection statements are explained with the common if...then...else notation introduced in Algol
60. This notation allows the program to execute different blocks of code based on conditions:

if condition then statement


else if condition then statement
else if condition then statement
...
else statement

In this structure:
• The if clause checks a condition, and if it’s true, the corresponding then clause is executed.
• The else if clauses allow for additional conditions to be checked sequentially.
• The else clause is executed if none of the previous conditions are met.

Algol 60 and Pascal require both the then clause and the else clause to contain a single statement,
which could be a compound statement enclosed in begin...end. This helps avoid grammatical
ambiguity in the code. For example, Algol 60 requires that the statement after the then keyword begin
with something other than if, preferring to begin with begin to avoid confusion. Pascal, on the other
hand, uses a disambiguating rule that associates an else with the closest unmatched then.

Modula-2 handle nested if statements with terminators, using a special elsif or elif keyword to prevent
a buildup of terminators:

IF a = b THEN ...
ELSIF a = c THEN ...
ELSIF a = d THEN ...
ELSE ...
END

This structure simplifies handling multiple conditions in a clear and organized way.

Cond in Lisp:
The equivalent construct in Lisp is shown using the cond function:

(cond
((= A B)

Unit: 5 - Control Flow: Structured and Unstructured 23


DCA1209: Principle of Programming Languages

(...))
((= A C)
(...))
((= A D)
(...))
(T
(...)))

In this Lisp structure:


• ‘Cond’ takes a sequence of pairs as arguments.
• Each pair's first element is a condition, and the second element is an expression to be returned
if that condition is true.
• The last pair typically has T as the condition, which means "true" in most Lisp dialects,
ensuring that if no other conditions are met, the final expression is executed.

5.1. Short-Circuited Conditions


The evaluation of Boolean conditions in programming, specifically in the context of if...then...else
statements. Typically, the condition is a Boolean expression, and there's often no need to evaluate the
entire expression to store a Boolean value. Instead, machines can use conditional branch instructions
to handle simple comparisons. This process allows for the creation of efficient code called "jump
code."

Jump Code: Jump code is used for expressions that can be short-circuited, meaning the evaluation
can stop as soon as the result is determined. For example, in a conditional expression like if (A > B)
and (C > D) or (E ≠ F), evaluation can stop once the first part of the condition is determined to be
false. This approach avoids unnecessary evaluations and can significantly improve efficiency.

Example: Code Generation for a Boolean Condition


if (A > B) and (C > D) or (E ≠ F) then
then_clause
else
else_clause

In Pascal, which does not use short-circuit evaluation, the code would load and compare each part of
the condition sequentially:
r1 := A

Unit: 5 - Control Flow: Structured and Unstructured 24


DCA1209: Principle of Programming Languages

r2 := B
r1 := r1 > r2
r2 := C
r3 := D
r2 := r2 > r3
r1 := r1 & r2
r2 := E
r3 := F
r2 := r2 ≠ r3
r1 := r1 or r2
if r1 = 0 goto L2
L1: then_clause
goto L3
L2: else_clause
L3:

In jump code, inherited attributes of the condition’s root would indicate that control should fall
through to the then_clause if the condition is true or branch to else_clause if it is false:
r1 := A
r2 := B
if r1 <= r2 goto L4
r1 := C
r2 := D
if r1 <= r2 goto L2
L4: r1 := E
r2 := F
if r1 ≠ r2 goto L1
L2: else_clause
goto L3
L1: then_clause
L3:

Example: Short-Circuit Creation of a Boolean Value


found_it := p /= null and then p.key = val;

Unit: 5 - Control Flow: Structured and Unstructured 25


DCA1209: Principle of Programming Languages

This is equivalent to:


if p /= null and then p.key = val then
found_it := true;
else
found_it := false;
end if;
Translated to jump code:
r1 := p
if r1 = 0 goto L1
r2 := r1→key
if r2 ≠ val goto L1
r1 := 1
goto L2
L1: r1 := 0
L2: found_it := r1

Short-circuit evaluation allows for more efficient code by stopping the evaluation of an expression as
soon as the result is determined. This feature is beneficial in programming languages and is
commonly used in conditional statements, local scopes for loop indices, and parameter modes in
various languages like Pascal and Ada. The design and implementation section emphasize that short-
circuit evaluation is both semantically useful and faster than alternatives, providing a significant
advantage in programming.

5.2. Case/Switch Statements


The case statement in programming languages is an alternative to using a series of nested
if...then...else statements. It simplifies the control flow when the same variable (like i in the examples)
is compared against different values.

The below example shows a sequence of if, elsif, and else conditions that check the value of i and
execute different blocks of code (clause_A, clause_B, etc.) based on the condition that is met. This can
be written more concisely using a case statement, which directly maps the possible values of i to the
respective code blocks.

In Modula-2, for example, the if sequence:


IF i = 1 THEN

Unit: 5 - Control Flow: Structured and Unstructured 26


DCA1209: Principle of Programming Languages

clause_A
ELSIF i IN 2, 7 THEN
clause_B
ELSIF i IN 3..5 THEN
clause_C
ELSIF (i = 10) THEN
clause_D
ELSE
clause_E
END

Can be rewritten as a case statement:


CASE i OF
1: clause_A
| 2, 7: clause_B
| 3..5: clause_C
| 10: clause_D
ELSE clause_E
END

When translating high-level control flow statements like if-else or case statements into low-level code,
jump tables can be used to optimize the execution. The images illustrate how the if-else chain can be
converted into a sequence of goto statements that correspond to different labels (L1, L2, etc.). A jump
table is essentially an array of pointers to these labels, allowing the program to quickly jump to the
correct block of code based on the value of the expression being evaluated.

For instance, in the low-level representation:


r1 := ... -- calculate tested expression
if r1 ≠ 1 goto L1
clause_A
goto L6
L1: if r1 ≠ 2 goto L2
L2: clause_B
goto L6
...

Unit: 5 - Control Flow: Structured and Unstructured 27


DCA1209: Principle of Programming Languages

This shows how the value of r1 is compared, and depending on the result, the code branches to the
corresponding clause, or falls through to the next condition. The jump table at label T maps the
possible values of r1 directly to their respective code labels, which significantly speeds up the decision
process.

The jump table concept involves indexing directly into an array of addresses (labelled &L1, &L2, etc.)
that correspond to the case arms. The table ensures that the execution jumps to the correct block of
code based on the calculated expression. For instance, if r1 is 3, the code might jump to the address
corresponding to &L3.

This method reduces the need for multiple conditional checks and makes the case handling more
efficient, especially in compiled languages where execution speed is critical.

i. Alternative Implementations
Alternative Implementations of Case Statements are:
• Linear Jump Table: This is an efficient method when the case statement labels are densely
packed and do not contain large ranges. It uses a table where each entry corresponds to a case
label, pointing directly to the code block associated with that label. This method can consume
a lot of memory if the case labels are sparse or if there are large gaps between values.
• Sequential Testing (Linear Search): This method is similar to a chain of if...then...else
statements. It has a time complexity of O(n), where n is the number of labels. This method is
simple but not efficient for a large number of case labels, especially if the labels are widely
spread out.
• Hash Function: A hash function can be used to compute the address to jump to, making the
execution faster. However, this method is not ideal when there are large gaps in the label
values or when labels are spread across a wide range.

ii. Syntax and Label Semantics


• Syntax Variations: Different languages have varied syntax rules for case statements. For
example, Pascal requires the use of begin...end delimiters to enclose multiple statements
within a single case arm. On the other hand, languages like Modula, Ada, and Fortran 90 expect
case arms to contain a list of statements by default.
• Label Requirements: The semantics of labels also differ by language. For instance, Pascal
does not automatically include a default clause for missing values in a case statement. If the
expression does not match any label, a dynamic semantic error is generated. Most compilers
prompt the programmer to add a default clause, often labeled as else or otherwise. Modula,

Unit: 5 - Control Flow: Structured and Unstructured 28


DCA1209: Principle of Programming Languages

however, allows an optional else clause, and Ada requires that each label's range of values is
explicitly covered, with no default or else case allowed if a value is missing.
• Binary Search: This method involves splitting the labels into two halves and repeatedly
narrowing down the search range until the correct label is found. It is more efficient than
sequential testing, with a time complexity of O(log n), making it suitable for cases with large
label ranges.

iii. The C switch Statement


The case or switch statement provides an alternative syntax for handling multiple conditional
branches, which is more structured and readable than nested if...then...else statements. When you
compare a variable against multiple constants, the case statement simplifies the syntax by
allowing each condition to be tested sequentially or via a computed jump.

Example:
switch (... /* tested expression */) {
case 1:
clause_A
break;
case 2:
case 7:
clause_B
break;
case 3:
case 4:
case 5:
clause_C
break;
case 10:
clause_D
break;
default:
clause_E
break;
}

Unit: 5 - Control Flow: Structured and Unstructured 29


DCA1209: Principle of Programming Languages

This C-style switch statement allows the program to jump to different parts of the code based on
the value of the expression. The break statement is crucial because it prevents the execution from
"falling through" to the next case label unintentionally, which can lead to bugs if not handled
properly.

Fall-through Behavior:
The concept of "fall-through" is explicitly shown in the case where a single label (like case 'A') does
not end with a break statement, allowing the execution to proceed to the next case (case 'a'). While
this behavior can be useful, it often leads to difficult-to-diagnose bugs, so modern languages like C#
require every case to end with a break, goto, continue, or return statement.

Historical Origins:
The case statement evolved from the computed goto statements in early programming languages like
Fortran and Algol 60. These constructs allowed programmers to perform multiway branching based
on integer values, but they were less structured and more prone to errors.

For example:
goto (15, 100, 150, 200), I

This statement directs the program to jump to different labeled points based on the value of I. This
form of branching was eventually replaced by more readable and maintainable case or switch
statements.

Design and Implementation Considerations:


The design of the case statement in languages like C and C++ is driven by implementation efficiency,
particularly in generating jump tables, which provide fast access to the corresponding code for each
case. The use of ranges in label lists (not allowed in Pascal or C) can reduce efficiency slightly, but
binary search is still much faster than using a series of if statements.

Unit: 5 - Control Flow: Structured and Unstructured 30


DCA1209: Principle of Programming Languages

6. SUMMARY
In this unit, learners have studied the foundational aspects of expressions and control flow in
programming languages. They explored the importance of precedence and associativity in
determining the order of operations within expressions, ensuring that complex expressions are
evaluated correctly.

Learners also explored the assignments, examining how values are stored and manipulated in
variables through various types of assignment operators.

Learners have learned the differences between structured and unstructured flow, understanding the
benefits and drawbacks of each approach in programming. The concept of sequencing was covered
to illustrate how the order of operations affects the flow of a program, while selection statements
such as if-else and case/switch statements were analyzed to demonstrate how decisions are made in
code.

Learners also emphasized the role of short-circuited conditions, which allow for more efficient
evaluations of logical expressions by avoiding unnecessary calculations.

Unit: 5 - Control Flow: Structured and Unstructured 31


DCA1209: Principle of Programming Languages

7. SELF-ASSESSMENT QUESTIONS
Multiple choice Questions
1 Which of the following determines the order in which operations are performed in an
expression?
A) Syntax
B) Precedence
C) Compilation
D) Debugging
2 In most programming languages, which operator usually has the highest precedence?
A) Addition (+)
B) Multiplication (*)
C) Parentheses (())
D) Assignment (=)
3 What does associativity determine in an expression?
A) The order of operations
B) How operators of the same precedence are grouped
C) The scope of variables
D) The type of variable
4 In which direction does the assignment operator typically associate?
A) Left to right
B) Right to left
C) Top to bottom
D) None of the above
5 Which of the following is a characteristic of unstructured flow?
A) Use of loops
B) Use of functions
C) Use of goto statements
D) Use of if-else statements
6 Which of the following best describes sequencing in programming?
A) Repeating a block of code
B) Executing code statements one after another

Unit: 5 - Control Flow: Structured and Unstructured 32


DCA1209: Principle of Programming Languages

C) Jumping between different sections of code


D) Parallel execution of code
7 The ‘if...else if...else’ structure is an example of which programming concept?
A) Looping
B) Selection
C) Sequencing
D) Recursion
8 Which statement is true about short-circuited conditions?
A) All parts of the condition are always evaluated.
B) Only the first part of the condition is evaluated if it determines the outcome.
C) They are slower than non-short-circuited conditions.
D) They are only available in assembly languages.
9 In a switch statement, what happens when there is no break statement at the end of a case?
A) The program stops executing.
B) The next case statement is automatically executed (fall-through).
C) The switch statement terminates.
D) The default case is executed.
10 Which of the following can replace nested if-else statements in some programming
languages?
A) Goto statement
B) Case/Switch statement
C) Loop statement
D) Function call

Unit: 5 - Control Flow: Structured and Unstructured 33


DCA1209: Principle of Programming Languages

8. GLOSSARY
Financial Management is concerned with the procurement of the least cost funds, and its effective

A combination of variables, operators, and values that yields a result


Expression -
when evaluated.

A symbol that performs a specific operation, such as addition (+),


Operator -
subtraction (-), or multiplication (*), on operands in an expression.

The order in which operators are evaluated in an expression. Operators


Precedence - with higher precedence are evaluated before operators with lower
precedence.

The rule that determines the direction (left-to-right or right-to-left) in


Associativity -
which operators of the same precedence are evaluated.

The process of assigning a value to a variable using the assignment


Assignment -
operator (=).

A programming paradigm that emphasizes the use of well-defined control


Structured Flow - structures (e.g., loops, conditionals) to organize and control the flow of a
program.
A programming style that relies on goto statements and similar
Unstructured Flow - constructs, often leading to "spaghetti code" that is difficult to follow and
maintain.

A control flow statement that allows the program to jump to another part
Goto Statement -
of the code unconditionally, often leading to unstructured flow.

The execution of statements in a specific order, one after another, as they


Sequencing -
appear in the code.

A group of statements enclosed within delimiters (e.g., {} in C/C++) that


Block
are executed together as a single unit.

A control flow mechanism that allows the program to choose between


Selection
different paths of execution based on a condition (e.g., if-else statements).

Short-Circuit A feature in logical operations where the evaluation stops as soon as the
Evaluation result is determined, without evaluating the rest of the expression.

Unit: 5 - Control Flow: Structured and Unstructured 34


DCA1209: Principle of Programming Languages

A data structure used in the implementation of a switch statement,


Jump Table
allowing for efficient jump to code addresses based on case values.

A control statement used to exit a loop or a switch-case structure


Break Statement
prematurely.

The behavior in a switch statement where, in the absence of a break, the


Fall-Through -
program continues executing the subsequent case labels.

Unit: 5 - Control Flow: Structured and Unstructured 35


DCA1209: Principle of Programming Languages

9. TERMINAL QUESTIONS
1. Explain the concept of operator precedence and associativity. How do they influence the
evaluation of expressions in programming languages?
2. Discuss the differences between structured and unstructured flow in programming. Why is
structured programming generally preferred?
3. What is the role of the assignment operator in programming? Provide examples of different types
of assignments.
4. Describe the importance of sequencing in imperative programming. How does it affect program
execution?
5. What are short-circuited conditions? How do they optimize the evaluation of Boolean expressions?
6. Compare and contrast the use of if-else statements and switch-case statements in programming.
Under what circumstances is one preferred over the other?
7. Discuss the concept of assignment in programming languages. How do different languages handle
assignments, and what are the implications of these differences?
8. Explain the concept of jump tables in the context of switch statements. How do jump tables
contribute to the efficiency of switch-case constructs?
9. Analyze the role of short-circuited conditions in optimizing program performance. Provide
examples to illustrate how short-circuited evaluation works in different programming languages.
10. Explore the importance of expression components in programming.

Unit: 5 - Control Flow: Structured and Unstructured 36


DCA1209: Principle of Programming Languages

10. ANSWERS
Self-Assessment Questions
1. Precedence
2. Parentheses (())
3. How operators of the same precedence are grouped
4. Right to left
5. Use of goto statements
6. Executing code statements one after another
7. Selection
8. Only the first part of the condition is evaluated if it determines the outcome.
9. The next case statement is automatically executed (fall-through).
10. Case/Switch statement

Terminal Questions Answers


1. Refer to Section 2.1
2. Refer to Section 3
3. Refer to Section 2.2
4. Refer to Section 4
5. Refer to Section 5.1
6. Refer to Sections 5 and 5.2
7. Refer to Section 2.2
8. Refer to Section 5.2
9. Refer to Section 5.1
10. Refer to Section 2

Unit: 5 - Control Flow: Structured and Unstructured 37


DCA1209: Principle of Programming Languages

11. REFERENCES
1. Michael L.Scott, “Programming Language Pragmatics”, 2015, Fourth Edition, Morgan Kaufmann
publisher.
2. Robert W. Sebesta, “Concepts of Programming Languages”, 2012, Tenth Edition, . ISBN 10: 0-13-
139531-9.

Unit: 5 - Control Flow: Structured and Unstructured 38

You might also like