COP4020 Programming Assignment 1 - Spring 2011

Similar documents
COP4020 Programming Languages. Functional Programming Prof. Robert van Engelen

LECTURE 16. Functional Programming

Project 2: Scheme Interpreter

6.184 Lecture 4. Interpretation. Tweaked by Ben Vandiver Compiled by Mike Phillips Original material by Eric Grimson

1 Lexical Considerations

Scheme Tutorial. Introduction. The Structure of Scheme Programs. Syntax

Principles of Programming Languages Topic: Functional Programming Professor L. Thorne McCarty Spring 2003

Scheme: Expressions & Procedures

Functional Programming. Pure Functional Programming

Functional Programming

Lexical Considerations

Scheme. Functional Programming. Lambda Calculus. CSC 4101: Programming Languages 1. Textbook, Sections , 13.7

Scheme in Scheme: The Metacircular Evaluator Eval and Apply

Functional Programming. Big Picture. Design of Programming Languages

Scheme: Data. CS F331 Programming Languages CSCE A331 Programming Language Concepts Lecture Slides Monday, April 3, Glenn G.

Lexical Considerations

Lecture 09: Data Abstraction ++ Parsing is the process of translating a sequence of characters (a string) into an abstract syntax tree.

6.037 Lecture 4. Interpretation. What is an interpreter? Why do we need an interpreter? Stages of an interpreter. Role of each part of the interpreter

CS 314 Principles of Programming Languages

Functional Programming. Pure Functional Languages

Organization of Programming Languages CS3200/5200N. Lecture 11

Scheme: Strings Scheme: I/O

Functional Programming Languages (FPL)

Notes on Higher Order Programming in Scheme. by Alexander Stepanov

An Introduction to Scheme

Typescript on LLVM Language Reference Manual

Functional Programming. Pure Functional Languages

11/6/17. Functional programming. FP Foundations, Scheme (2) LISP Data Types. LISP Data Types. LISP Data Types. Scheme. LISP: John McCarthy 1958 MIT

Features of C. Portable Procedural / Modular Structured Language Statically typed Middle level language

FP Foundations, Scheme

Introduction to Scheme

CSc 520 Principles of Programming Languages

CS 314 Principles of Programming Languages. Lecture 16

Summer 2017 Discussion 10: July 25, Introduction. 2 Primitives and Define

Design Issues. Subroutines and Control Abstraction. Subroutines and Control Abstraction. CSC 4101: Programming Languages 1. Textbook, Chapter 8

Deferred operations. Continuations Structure and Interpretation of Computer Programs. Tail recursion in action.

UNIVERSITY of CALIFORNIA at Berkeley Department of Electrical Engineering and Computer Sciences Computer Sciences Division

Lecture08: Scope and Lexical Address

CSC 533: Programming Languages. Spring 2015

Scheme as implemented by Racket

Comp 311: Sample Midterm Examination

LECTURE 17. Expressions and Assignment

CS61A Midterm 2 Review (v1.1)

Introduction to ML. Mooly Sagiv. Cornell CS 3110 Data Structures and Functional Programming

A Brief Introduction to Scheme (II)

Principle of Complier Design Prof. Y. N. Srikant Department of Computer Science and Automation Indian Institute of Science, Bangalore

Why do we need an interpreter? SICP Interpretation part 1. Role of each part of the interpreter. 1. Arithmetic calculator.

Announcements. The current topic: Scheme. Review: BST functions. Review: Representing trees in Scheme. Reminder: Lab 2 is due on Monday at 10:30 am.

MIT Scheme Reference Manual

CS 415 Midterm Exam Spring SOLUTION

CS 61A Interpreters, Tail Calls, Macros, Streams, Iterators. Spring 2019 Guerrilla Section 5: April 20, Interpreters.

Modern Programming Languages. Lecture LISP Programming Language An Introduction

for (i=1; i<=100000; i++) { x = sqrt (y); // square root function cout << x+i << endl; }

Scheme Quick Reference

Java+- Language Reference Manual

Scheme Quick Reference

Normal Order (Lazy) Evaluation SICP. Applicative Order vs. Normal (Lazy) Order. Applicative vs. Normal? How can we implement lazy evaluation?

An introduction to Scheme

Discussion 12 The MCE (solutions)

Introduction to ML. Mooly Sagiv. Cornell CS 3110 Data Structures and Functional Programming

Typed Racket: Racket with Static Types

University of Massachusetts Lowell

Chapter 15. Functional Programming Languages

COP4020 Programming Languages. Control Flow Prof. Robert van Engelen

Turtles All The Way Down

Intro. Scheme Basics. scm> 5 5. scm>

The Typed Racket Guide

CS 61A, Fall, 2002, Midterm #2, L. Rowe. 1. (10 points, 1 point each part) Consider the following five box-and-arrow diagrams.

A Simple Syntax-Directed Translator

CS4120/4121/5120/5121 Spring 2016 Xi Language Specification Cornell University Version of May 11, 2016

COSE212: Programming Languages. Lecture 3 Functional Programming in OCaml

for (i=1; i<=100000; i++) { x = sqrt (y); // square root function cout << x+i << endl; }

Chapter 11 :: Functional Languages

Imperative, OO and Functional Languages A C program is

Documentation for LISP in BASIC

Functional programming with Common Lisp

CPS 506 Comparative Programming Languages. Programming Language Paradigm

Introduction to ML. Mooly Sagiv. Cornell CS 3110 Data Structures and Functional Programming

4/19/2018. Chapter 11 :: Functional Languages

CS1622. Semantic Analysis. The Compiler So Far. Lecture 15 Semantic Analysis. How to build symbol tables How to use them to find

Functional Programming Lecture 1: Introduction

Principles of Programming Languages 2017W, Functional Programming

CS 314 Principles of Programming Languages

UNIT- 3 Introduction to C++

Building a system for symbolic differentiation

NOTE: Answer ANY FOUR of the following 6 sections:

COP4020 Spring 2011 Midterm Exam

Common LISP-Introduction

4.2 Variations on a Scheme -- Lazy Evaluation

Programming Project #4: A Logo Interpreter Summer 2005

Programming Languages

Common LISP Tutorial 1 (Basic)

Language Reference Manual

C Language Part 1 Digital Computer Concept and Practice Copyright 2012 by Jaejin Lee

Revised 5 Report on the Algorithmic Language Scheme

CS 415 Midterm Exam Spring 2002

Control in Sequential Languages

c) Comments do not cause any machine language object code to be generated. d) Lengthy comments can cause poor execution-time performance.

Decaf Language Reference Manual

Example Scheme Function: equal

Transcription:

COP4020 Programming Assignment 1 - Spring 2011 In this programming assignment we design and implement a small imperative programming language Micro-PL. To execute Mirco-PL code we translate the code to Scheme and execute it in Scheme. This translator/compiler for Micro-PL will be written in Scheme. The objective of this assignment is to apply programming language design principles, implement program translation, and practice Scheme programming. For the Scheme programming we use the most common Scheme programming constructs discussed in class and some extras that will be explained in this document. A New Programming Language Micro-PL Our Micro-PL programming language is imperative. It adopts a procedural style programming with side effects (explicit state changes by variable assignment). Our Micro- PL programming language supports assignments to variables, function calls (including functions to perform arithmetic), programming statement sequences, if-then-else control flow, and loops. Micro-PL is dynamically typed, so any type of value can be assigned to a variable as long as the variable is d in the outer scope. The syntax of Micro-PL is relatively simple and is designed to simplify reading and parsing of Micro-PL programs by our translator/compiler. By keeping the syntax simple we do not need to implement advanced parsing techniques for our translator. Our translator looks for keyword commands to trigger a translation to Scheme. As a consequence, Micro-PL programs have a command-based appearance. Each programming statement starts with a keyword command. The following commands are supported. The set command assigns a value to a variable. For example: set greeting = Hello, world! assigns a string value to foo. The command is used to sequence a series of statements in a... block, for example: set foo = 0 set bar = 1 1

which is similar to all Algol-like programming languages (C/C++ and Java use {... } instead). In our Micro-PL programming language we do not use semicolons to separate or terminate statements (taking a neutral position in the semicolon wars). Statements are simply chained together by white space (blanks, tabs, and newlines). Local variables are d with the command that is followed by a... block in which the variables are visible (are in scope ). Thus, variables have a local scope, limited to the scope of the block. Each variable must be initialized. This requirement prevents any accidental use of uninitialized variables and values. (The use of uninitialized values is a typical programming error that can sometimes, but not always, be flagged by a compiler.) For example: foo = 0 bar = 1 set foo = bar where foo and bar have a local scope and are initialized to 0 and 1, respectively. The value of foo is reassigned in the block. The local scope means that the values of foo and bar are not accessible outside of the scope of the...... block. The if command starts an if-then-else statement, where each then and else clause is a single statement (use... when needed). For example: foo = #f bar = -1 if foo then set bar = 0 else set bar = 1 which uses the Scheme value #f for false (and likewise we will use #t for true). This program assigns 1 to bar, since the condition is false and the else-clause is executed. The for command executes a Pascal-like loop with an integer-valued loop counter that ranges from a starting value to an ing value. The loop body is a single statement (use... when needed). For example: for i = 1 to 10 set foo = i 2

Just as in many newer programming languages, such as Ada, the loop counter is locally d and has a local scope in the loop body. This means that the loop counter is only visible in the loop body and its value cannot be accessed after the loop body. Finally, a statement can be an expression. An expression is either: A constant. Any Scheme constant can be used, such as integer, float, #f and #t, string, and atom (a quoted name such as a and also ()). A variable. A function call of the form call name arg 1... arg n $ where name is a function name or operator and arg i, i 0, are expressions. For example: if call = foo 0 $ then call display foo is zero $ else call display foo is non-zero $ where display is the Scheme function to display the argument on the terminal. All Scheme functions can be called this way. A sample of some useful built-in Scheme functions that can be called using this syntax are: Function call Description call display value $ display value call newline $ advance to new line call list value 1... value n $ construct a list of n values call cons value list $ construct a list node call car list $ first value (head) of a list call cdr list $ tail list call cadr list $ second value of a list call caddr list $ third value of a list call length list $ length of a list call + value 1... value n $ sum values ( subtracts) call value 1... value n $ multiply values (/ divides) call < value 1... value 2 $ compare values (<, <=, =, >, >=) call eq? value 1... value 2 $ true when equal call equal? value 1... value 2 $ true when structurally equal call and value 1... value n $ logical and call or value 1... value n $ logical or 3

The absence of parenthesis and commas to pass arguments is indicative of modern functional languages such as Haskell. Because our simple syntax does not need grouping with parenthesis, the use of the call needs a terminator $ to the arguments. This is also the case for nested expressions, where we pass arguments that are function calls. This rule is consistently applied, though for arithmetic operations this can look a bit odd. For example, to compute y = x 2 x we write: set y = call call * x x $ x $ which is due to the prefix notation (also in Scheme we use prefix notation and write this as (- (* x x) x) which shows that call acts as the opening parenthesis and $ as the closing parenthesis in Micro-PL). In the following sections we develop a translator to convert Micro-PL to an s-expression. The s-expression represents a valid Scheme program that can be executed. Reading and Translating Micro-PL to Scheme We need the following Scheme functions for file I/O: (set-current-input-port! (open-input-file filename)) opens filename for reading so that subsequent (read) calls scan the input file and return tokens (atoms and constants). (read) returns the next token from the file, where a token is a Scheme atom or constant. eof-object? token is true of token is an -of-file marker. Here is an example use of these functions to read a file and construct a list of tokens: ; file2list takes a file name and returns a list of tokens (define file2list (lambda (filename) ( (set-current-input-port! (open-input-file filename)) (read-list)))) ; read-list returns a list of tokens read from the current input port (define read-list (lambda () (let ((token (read))) (cond ((eof-object? token) ()) (else (cons token (read-list))))))) 4

If you are not familiar with the, let, cond, and the if special forms, then this is a good time to read up on Scheme using the lecture notes and textbook. We observer the following: file2list returns the value of (read-list), but opens the file first and assigns it to the current input port before reading starts; the let in read-list assigns the current input token value (a Scheme atom or constant) to the token local variable (since let takes a list of variable-value pairs, we must use ((token read)) to enclose the pair in the list); if the token is an -of-file marker then read-list returns an empty list (). This is the base case of recursion where we have no (further) input and return an empty list; if the token is not an -of-file marker then we construct a new list node with the token value and a tail that is recursively constructed by subsequent read-list calls. The first function that we will write for our Micro-PL to s-expression translator is similar to file2list, except that it invokes statement to read and convert the token sequence of a Micro-PL program statement: (define convert (lambda (filename) ( (set-current-input-port! (open-input-file filename)) (statement (read))))) where statement takes the first token and continuous to read new tokens until the of the statement and returns the s-expression converted from this token stream. Therefore, if we load our translator into Scheme and then enter the convert command from the Scheme command line (assuming our translator program is stored in file mytrans.scm): 1 ]=> (load "mytrans")... 1 ]=> (convert "mytestprog") we should see the s-expression representation of the Micro-PL program mytestprog. How the statement function in convert should convert tokens to an s-expression is discussed next. 5

Micro-PL Program Statements To translate a statement, we need to check the token for a matching keyword. If there is no match we assume the statement is an expression: (define statement (lambda (token) (cond ((eq? token ) ( statement)) ((eq? token ) ( statement)) ((eq? token for) (for statement)) ((eq? token if) (if statement)) ((eq? token set) (set statement)) (else (expression token))))) Each function is responsible for reading the entire sequence of tokens that represents that programming construct and to return the s-expression that is a valid Scheme program. In case a statement is an expression, we already read the first token and therefore pass it as an argument to the expression function for further processing. We can implement the statement function as follows: (define statement (lambda () (let* ( (d (declarations)) (s (statements))) (cons let* (cons d s))))) where we used the Scheme let* to ensure that each variable-value pair is assigned in order. Scheme function arguments are not evaluated in order, nor are let pairs evaluated in order! For example, (list (read) (read) (read)) does not ensure that the three values are stored in the list in the order they appear in the file! In fact, most programming languages (C/C++ also) leave the evaluation of arguments unspecified, which allows the compiler to optimize the code more aggressively. The declarations function should recursively convert the variable = expression pairs to a list of pairs (each pair is a list of two elements) until the keyword is reached. More formally we can write this as the inductive solution to the list D of declarations: D = { () if token = or EOF (cons pair D) otherwise 6

where pair is a list of two elements produced by another function that takes the current token, considers it to be the variable name v, skips over the = in the input, and calls expression to convert the expression to an s-expression e to form the pair = (v e). The statements function should recursively convert a sequence of statements into a list until the keyword is reached. More formally, we can write this as the inductive solution to the list S of statements. { () if token = or EOF S = (cons s S) otherwise where s is a statement converter for which we defined the statement function above (that takes the current token as argument). Again, when necessary we must use let* in our implementation to ensure the proper ordering of function calls that perform I/O before we pass the results to other functions. With the above, the aim is to convert v 1 = e 1. v n = e n s 1. s m into the s-expression (let* ((v 1 e 1 )... (v n e n )) s 1... s m ) where the e i and s j are converted from Micro-PL to s-expressions before we put them into the let* s-expression. All nested conversions should involve (recursive) function calls. You should not use non-pure (destructive) functions such as set-car! to modify data! You can simply call the conversion functions for expressions and statements and put the result in the s- expression under construction. 7

Likewise, we can convert a... block: s 1. s m into the s-expression ( s 1... s m ) We convert the conditional if-then-else if e then s 1 else s 2 into the s-expression (if e s 1 s 2 ) where e, s 1, and s 2 are converted to s-expressions. We convert the loop for v = e 1 to e 2 s into the s-expression (do ((v e 1 (+ v 1))) ((> v e 2 ) v) s) where e 1, e 2, and s are converted to s-expressions. 8

We convert the assignment statement set v = e into the s-expression (set! v e) where e is converted to s-expression. Since this one is simple to implement. Here is a possible implementation: (define set statement (lambda () (let ( (token (read))) (list set! token (expression (read-after =)))))) Note the use of let to ensure we read the token before we call read-after and expression. The read-after function skips a token and reads the next: (define read-after (lambda (token) (if (eq? (read) token) (read) (display Syntax error )))) so that an error message is displayed when there is no match. Micro-PL Expressions and Function Calls To convert Micro-PL expressions to s-expressions we only need to capture the call construct and convert it. Otherwise, we just return the token (which is an atom or constant) as the s-expression: (define expression (lambda (token) (if (eq? token call) (call expression) token))) 9

so that call expression returns a list with the function name and s-expressions for the arguments. That is, we convert call f e 1... e n $ into the s-expression (f e 1... e n ) where the e i are converted to s-expressions. Converting a sequence of expressions to a list of s-expressions is similar to converting a sequence of statements to s-expressions, so it seems natural to exploit the same implementation approach. Executing Micro-PL Programs To convert the Micro-PL program and execute the resulting s-expression, we use eval from the Scheme command line: (eval (convert mytestprog ) (the-environment)) If there is an error in the s-expression (meaning it is not a valid Scheme program) or if variables and functions are used that have not been d then an error message will be generated. Otherwise, the program executes and its result value is printed. Code Examples call display Hello, world! $ call newline $ Converted to s-expression: ( (display "Hello, world!") (newline)) 10

greeting = Hello, world! call display greeting $ call newline $ Converted to s-expression: (let* ((greeting "Hello, world!")) (display greeting) (newline)) x = 10 call display call * 2 x $ $ call newline $ Converted to s-expression: (let* ((x 10)) (display (* 2 x)) (newline)) num = 10 fac = 1 for i = 1 to num set fac = call * i fac $ call display fac $ call newline $ Converted to s-expression: (let* ((num 10) (fac 1)) (do ((i 1 (+ i 1))) ((> i num) i) (set! fac (* i fac))) (display fac) (newline)) 11

if call eq? call read $ yes $ then call display OK $ else call display Why not? $ Converted to s-expression: (if (eq? (read) (quote yes)) (display "OK") (display "Why not?")) weekdays = (mon tue wed thu fri) days = call app weekdays (sat sun) $ party = () set party = call member fri days $ call display Going out on $ for i = 0 to call call length party $ 1 $ day = call list-ref party i $ call display day $ call display $ call newline $ Converted to s-expression: (let* ((weekdays (quote (mon tue wed thu fri))) (days (app weekdays (quote (sat sun)))) (party (quote ()))) (set! party (member (quote fri) days)) (display "Going out on ") (do ((i 0 (+ i 1))) ((> i (- (length party) 1)) i) (let* ((day (list-ref party i))) (display day) (display " "))) (newline)) 12