MODULE 1

Programming Language Principles & Foundations

Programming Paradigms

What is a Programming Paradigm?

A programming paradigm is a fundamental style or approach to programming that provides a conceptual framework for solving problems and structuring code. It defines how you think about and organize your program.

⚙️

Imperative

Step-by-step instructions
How to do something
Examples: C, Pascal

🏗️

Object-Oriented

Objects and classes
Data + methods together
Examples: Java, C++

🧮

Functional

Mathematical functions
No side effects
Examples: Scheme, Haskell

🧠

Logic

Facts and rules
What is true
Examples: Prolog, Datalog

Language Evaluation Criteria

Readability

How easy is it to understand the code?

  • Simple syntax and semantics
  • Good control structures
  • Meaningful data types
  • Clear naming conventions

Writability

How easy is it to write programs?

  • Expressiveness of language
  • Abstraction capabilities
  • Support for common patterns
  • Conciseness without cryptic syntax

Reliability

Does the program behave as expected?

  • Type checking (static vs dynamic)
  • Exception handling mechanisms
  • Memory safety features
  • Aliasing restrictions

Cost Factors

What are the economic implications?

  • Training programmers
  • Development time
  • Compilation time
  • Execution efficiency
  • Maintenance costs

Language Features vs Performance

The Trade-off Spectrum

Feature Benefit Performance Cost Example
Automatic Memory Management Programmer productivity, safety GC overhead, unpredictable pauses Java, Python
Dynamic Typing Flexibility, rapid prototyping Runtime type checks Python, JavaScript
Virtual Method Calls Polymorphism, extensibility Indirect function calls C++, Java
Bounds Checking Memory safety Array access overhead Java, C#

Historical Influences on Language Development

  • Computer Architecture: Von Neumann architecture influenced imperative languages
  • Mathematical Foundations: Lambda calculus led to functional languages
  • Problem Domains: Business (COBOL), Scientific (FORTRAN), AI (Lisp, Prolog)
  • Software Engineering Needs: Modularity, reusability, maintainability
  • Hardware Evolution: Multicore → parallel programming languages
  • Internet Era: Web development → JavaScript, PHP, etc.

Language Structure Levels

1. Lexical Structure

The vocabulary of the language - individual tokens

// Examples of lexical tokens: int // keyword variable // identifier 42 // integer literal "hello" // string literal + // operator ; // delimiter
  • Keywords (reserved words)
  • Identifiers (variable names)
  • Literals (constants)
  • Operators (+, -, *, /)
  • Separators/Delimiters (;, {, })

2. Syntactic Structure

How tokens combine to form valid statements

// Syntax rules define valid combinations: assignment_stmt ::= identifier = expression ; expression ::= term (+ | -) expression | term term ::= factor (* | /) term | factor factor ::= identifier | number | ( expression )
  • Grammar rules (production rules)
  • Parse trees and derivations
  • Precedence and associativity
  • Ambiguity resolution

3. Contextual Structure

Context-sensitive constraints and scoping rules

int x = 10; // x declared as int x = "hello"; // ERROR: type mismatch void func() { int y = 5; // y has local scope } print(y); // ERROR: y not in scope
  • Variable declarations before use
  • Type compatibility checking
  • Scope rules (lexical/dynamic)
  • Function signature matching

4. Semantic Structure

The meaning and behavior of valid programs

int a = 5, b = 0; int result = a / b; // Syntactically correct // Semantically: division by zero! while (true) { // Infinite loop // semantic issue: never terminates }
  • Runtime behavior
  • Memory allocation/deallocation
  • Control flow execution
  • Side effects and state changes

BNF & Syntax Representation

Backus-Naur Form (BNF)

BNF Notation Rules

<non-terminal> ::= production rule | means "or" (alternative) terminal symbols are in quotes or plain text <non-terminal> symbols need further definition

Simple Expression Grammar

<expression> ::= <term> | <expression> + <term> | <expression> - <term> <term> ::= <factor> | <term> * <factor> | <term> / <factor> <factor> ::= <number> | <identifier> | ( <expression> )

If-Statement Grammar

<if-stmt> ::= if ( <condition> ) <statement> | if ( <condition> ) <statement> else <statement> <condition> ::= <expression> <relop> <expression> <relop> ::= == | != | < | > | <= | >=

Extended BNF (EBNF)

EBNF Additional Notation

{ } means zero or more repetitions [ ] means optional (zero or one) ( ) means grouping
// Regular BNF: <var-list> ::= <variable> | <var-list> , <variable> // EBNF equivalent: <var-list> ::= <variable> { , <variable> }

Syntax Graphs (Railroad Diagrams)

  • Visual representation of grammar rules
  • Rectangles = non-terminals
  • Circles/Ovals = terminals
  • Arrows show possible paths
  • Loops represent repetition
  • Branches represent alternatives

Processing Methodologies

Interpretation

Execute source code directly without creating machine code

  • Examples: Python, JavaScript, Shell scripts
  • Pros: Interactive, immediate execution, platform independent
  • Cons: Slower runtime, requires interpreter
  • Process: Source → Parse → Execute

Compilation

Translate source code to machine code before execution

  • Examples: C, C++, Rust, Go
  • Pros: Fast execution, optimized code, no runtime dependency
  • Cons: Longer build times, platform specific
  • Process: Source → Compile → Machine Code → Execute

Intermediate/Hybrid

Compile to intermediate code, then interpret or JIT compile

  • Examples: Java (bytecode), C# (IL), Python (.pyc)
  • Pros: Portable, some optimization, faster than pure interpretation
  • Cons: Requires runtime environment (JVM, CLR)
  • Process: Source → Bytecode → VM/Runtime → Execute

Compilation Process Steps

1. Preprocessing → Handle #include, #define, etc. 2. Lexical Analysis → Break into tokens 3. Syntax Analysis → Build parse tree 4. Semantic Analysis → Type checking, scope resolution 5. Optimization → Improve code efficiency 6. Code Generation → Produce target code 7. Linking → Combine object files

Macro Processing

What are Macros?

Macro Definition

Macros are text substitution mechanisms that are processed before compilation. The preprocessor literally replaces macro calls with their definitions.

Simple Macros

#define PI 3.14159 #define MAX_SIZE 100 // Usage: double area = PI * radius * radius; int buffer[MAX_SIZE]; // After preprocessing: double area = 3.14159 * radius * radius; int buffer[100];

Function-like Macros

#define MAX(a, b) ((a) > (b) ? (a) : (b)) #define SQUARE(x) ((x) * (x)) // Usage: int bigger = MAX(10, 20); int result = SQUARE(side); // After preprocessing: int bigger = ((10) > (20) ? (10) : (20)); int result = ((side) * (side));

Macros vs Functions

Key Differences

Aspect Macros Functions
Processing Time Compile-time (preprocessing) Runtime
Type Checking No type checking Strong type checking
Performance No function call overhead Function call overhead
Code Size Code bloat (inlined everywhere) One copy in memory
Debugging Harder to debug Easier to debug
Side Effects Can cause multiple evaluations Arguments evaluated once

Macro Pitfalls

Side Effect Problems

#define MAX(a, b) ((a) > (b) ? (a) : (b)) int x = 5, y = 10; int bigger = MAX(++x, ++y); // Expands to: int bigger = ((++x) > (++y) ? (++x) : (++y)); // Problem: x or y gets incremented twice!

Macros vs Inline Functions

Inline Functions (C++)

inline int max(int a, int b) { return (a > b) ? a : b; } // Benefits of inline over macros: // - Type checking // - No side effect issues // - Scope respecting // - Debugger friendly

When to Use Each

  • Use Macros for:
    • Constants
    • Conditional compilation
    • Code generation
    • Language features not available
  • Use Inline Functions for:
    • Type-safe operations
    • Complex logic
    • Avoiding side effects
    • Modern C++ code

Macro Processing Examples

Step-by-Step Expansion

#define PRINT_VAR(var) printf(#var " = %d\n", var) #define SWAP(a, b) { int temp = a; a = b; b = temp; } int x = 10, y = 20; PRINT_VAR(x); SWAP(x, y); PRINT_VAR(x); // After preprocessing: int x = 10, y = 20; printf("x" " = %d\n", x); { int temp = x; x = y; y = temp; }; printf("x" " = %d\n", x);

Error Classification by Structure Level

Lexical Errors

Invalid characters or tokens

// Illegal characters int x@ = 5; // @ not allowed float $price; // $ not valid in identifiers "unterminated string
Syntax Errors

Grammar rule violations

// Missing semicolons, brackets int x = 5 // missing ; if (x > 0 { // missing ) print(x)
Contextual Errors

Context-sensitive rule violations

// Type mismatches, scope errors int x; x = "hello"; // type error print(undeclared); // undeclared variable
Semantic Errors

Logical errors in program meaning

// Runtime errors, logic errors int x = 10 / 0; // division by zero while (true) { } // infinite loop int arr[5]; arr[10] = 0; // array bounds error

Error Detection Timeline

  • Lexical: Caught by lexer during tokenization
  • Syntax: Caught by parser during syntax analysis
  • Contextual: Caught during semantic analysis (compile-time)
  • Semantic: Often caught only at runtime

Key Takeaways for Module 1

  • Programming paradigms provide different mental models for problem-solving
  • Language design involves trade-offs between readability, writability, reliability, and performance
  • Four structure levels (lexical, syntax, contextual, semantic) each catch different error types
  • BNF and EBNF provide formal ways to specify language syntax
  • Processing methodologies (interpretation, compilation, hybrid) have different performance characteristics
  • Macros are powerful but dangerous - prefer inline functions when possible
  • Understanding language implementation helps you write better code and debug issues