The Language Parametric Read-Eval-Print Loop

Total Page:16

File Type:pdf, Size:1020Kb

The Language Parametric Read-Eval-Print Loop Towards a Language Parametric World: The Language Parametric Read-Eval-Print Loop Author: Jeroen Lappenschaar Supervisor: Paul Klint Amsterdam, June 16, 2014 Abstract The Read-Eval-Print Loop (REPL) has proven itself to be a useful tool for software developers. New languages, especially DSLs, often don't have these dedicated tools. This thesis researches whether a REPL can be parametrized for any language, and if so, how it should be instantiated. This is done in three parts: (1) An analysis is made of the features in a REPL and its language dependencies, (2) an overview of 13 popular REPLs and their features is given, and finally (3) the results of a proof-of-concept implementation are discussed. The thesis contributes with a domain analysis about the REPL and concludes with pointers on the parametrization of languages and their features. Contents 1 Introduction 3 1.1 Research . .4 1.1.1 Language-parametric REPL . .4 1.1.2 Exploratory research . .5 1.2 Set-up . .5 2 Background 6 2.1 An introduction . .6 2.2 The definition . .7 2.3 Implementation . .8 2.4 Related topics . .9 2.4.1 Language workbenches . .9 2.4.2 Reusable REPLs . .9 2.5 Summary . 10 3 A REPL Comparison 11 3.1 Hands-on . 11 3.2 Selection . 12 3.2.1 REPL Selection . 12 3.2.2 Feature Selection . 12 3.3 REPL feature overview . 13 3.4 Results . 17 4 Parametrization 19 4.1 REPL Parametrization . 19 4.2 Feature parametrization . 19 4.2.1 Available information . 20 4.2.2 Statement Actions . 20 4.2.3 Statement Features . 23 4.2.4 Information Features . 25 4.2.5 Session Actions . 28 4.2.6 Code-file Actions . 29 4.3 Dependency Conclusion . 31 4.4 Feature Relations Conclusion . 33 5 A LP-REPL Implementation 34 5.1 Environment set-up . 34 5.1.1 Java . 34 5.1.2 Rascal . 34 1 5.1.3 Evaluator integration . 35 5.2 The Implementation . 35 5.2.1 Metrics . 37 5.2.2 Architecture . 37 5.2.3 Basic REPL . 38 5.2.4 Features . 39 5.3 Resulting interfaces . 40 5.3.1 Command interface . 40 5.3.2 Java Evaluators . 42 5.3.3 Rascal DSL Evaluator . 42 5.3.4 Interface analysis . 43 5.4 Summary . 43 6 Conclusion 44 Bibliography 48 A Choice of REPLs 49 B REPL hands-on experience 50 C Java Interface 54 2 Chapter 1 Introduction Software development tools haven proven their worth from the day they were first created. They make problems more insightful (e.g., debuggers, profilers), they save developers from mind robbing tasks (e.g., automatic builders, unit testing), help or save time in any other matter (e.g., bug databases, code generation, code formatting). There are many more tools to name, and as many reasons for using them. Some of these tools can be used for any programming language (e.g., code revision, bug databases) but most of them are dedicated to a single language. With the development of a language the development of these tools often quickly follows. They provide an easier introduction to new developers and pave way for the adoption of the language. The development of these tools takes time however. Specifically for the development of Domain Specific Languages (DSLs) this is not desirable. These tools are meant to be quickly developed and quickly put to use. It is not desirable to keep creating dedicated development tools for every developed language. Language workbenches have been developed to solve exactly this problem. They offer the developer an IDE that directly integrates with the language that is being developed. Most of these workbenches already offer integrated features like syntax highlighting, code folding, error marking and refactoring [7]. A tool that has not yet been added to any of these language workbenches in a parametrized fashion is the Read-Eval-Print Loop (REPL). A REPL is often described as an interactive console where one can run code line by line and instantaneously see its result. It is the console that most people will know from languages like Haskell, Scala, Python or any of the LISP dialects. Figure 1.1 shows an example of a typical REPL. Figure 1.1: An example of a typical REPL. In this case we see a Linux terminal where the default Ruby REPL (irb) is running. The already run code shows how a user can test statements, create variables and call library functions. 3 The main advantage of a REPL is the direct interaction that it offers with code. To test just a specific function, a developer often creates a special main function, creates help functions and/or comments out any part of the code. The REPL replaces this necessity by facilitating access to any code in development and interaction with that code. This helps developers in the following cases: • Testing and debugging: One can quickly test any function with any parameter1. • Learning the language: The REPL instantaneously displays the result of the entered code, which is ideal for users who want to explore or experiment with the language. In short: \REPLs facilitate exploratory programming and debugging because the read-eval-print loop is usually much faster than the classic edit-compile-run-debug cycle." [34]. 1.1 Research So far we have seen the need for multi-language tools and a language specific tool, the REPL. A combination of this, a REPL that can be parametrized for different languages, would bring all the benefits of the REPL to any language. This would especially benefit new developed languages who need to be tested and learned by its users. To examine this, the research topic of this thesis will be: How can we design a language-parametric REPL, and how can it be instantiated for different languages? The purpose of this research is twofold: 1. To give an example implementation of a language parametrized REPL. 2. Let this be an explorative research for language parametrization of development tools in the scope of DSLs. Both purposes will be discussed next. 1.1.1 Language-parametric REPL Creating a language-parametric REPL will make all advantages of a REPL easily available for all languages. Next to the already mentioned advantages of a REPL this has an added benefit in some cases: • First, it supports language development. The language developer often has to test simple statements to see if everything (syntax, grammar, parser, interpreter/compiler) is working as hoped. As we have seen, the REPL is an ideal tool for this job. • Secondly, a new language needs to be learned by the users. In the case of DSLs there are higher chances that these people have no previous experience with programming. The experimental and explorative benefits of the REPL will help them here. • Lastly, the REPL offers a user-friendly interface for the new language that can instantly be accessed. 1Some REPLs even have the option to control the debugger from the console. 4 1.1.2 Exploratory research The second purpose of this thesis is to see how tools in general, can be made language- parametric. Information gathered here could be used to improve on existing language work- benches. The REPL, with all its features, affects many aspects of a language, and the analysis about this can give insight into: • What parts of a tool or feature are language specific. • How can the necessary information from the languages be shared. 1.2 Set-up To answer the research question, a domain analysis of the REPL is performed. This is done by performing background research in the literature (Chapter 2) and a thorough analysis of the most popular or outstanding REPLs (Chapter 3). It turns out that a big part of a REPL is formed by its many possible features. We defined these features and analysed how they could be parametrised for different languages (Chapter 4). All gathered information and analyses are then used to create a proof-of-concept implementation (Chapter 5) that will show a possible interface for a language-parametrized REPL. The gathered results show that in order to create a full-featured language-parametrized REPL one needs a lot of additional language-specific information. This information is however already often available in language workbenches and such a REPL could therefore be easily added to any existing language workbench. 5 Chapter 2 Background 2.1 An introduction The REPL has long been recognized as a useful tool. In the scientific literature it is mainly described in two papers that each discuss the development of an IDE. Those IDEs are DrScheme, an IDE for the Lisp-dialect Scheme, and DrJava, an IDE for Java created for educational purposes. Both papers affirm the beneficial features of the REPL, they say the following about the REPL: DrJava [2] The REPL offers the advantages of \alternative entry-points", \quickly access the various components of a program without recompiling it, or otherwise modifying the program text" and \also serves as a flexible tool for debugging and testing programs and experimenting with new libraries" DrScheme [8] \Interactivity is primarily used for program exploration, the pro- cess of evaluating expressions in the context of a program to determine its behaviour. Frequent program exploration during development saves large amounts of conven- tional debugging time. Programmers use interactive environments to test small components of their programs and determine where their programs go wrong. They also patch their programs with the REPL in order to test potential improvements or bug fixes by rebinding names at the top-level." The REPL was presumably introduced in the first version of Lisp and served as a way to start running the code.
Recommended publications
  • (Pdf) of from Push/Enter to Eval/Apply by Program Transformation
    From Push/Enter to Eval/Apply by Program Transformation MaciejPir´og JeremyGibbons Department of Computer Science University of Oxford [email protected] [email protected] Push/enter and eval/apply are two calling conventions used in implementations of functional lan- guages. In this paper, we explore the following observation: when considering functions with mul- tiple arguments, the stack under the push/enter and eval/apply conventions behaves similarly to two particular implementations of the list datatype: the regular cons-list and a form of lists with lazy concatenation respectively. Along the lines of Danvy et al.’s functional correspondence between def- initional interpreters and abstract machines, we use this observation to transform an abstract machine that implements push/enter into an abstract machine that implements eval/apply. We show that our method is flexible enough to transform the push/enter Spineless Tagless G-machine (which is the semantic core of the GHC Haskell compiler) into its eval/apply variant. 1 Introduction There are two standard calling conventions used to efficiently compile curried multi-argument functions in higher-order languages: push/enter (PE) and eval/apply (EA). With the PE convention, the caller pushes the arguments on the stack, and jumps to the function body. It is the responsibility of the function to find its arguments, when they are needed, on the stack. With the EA convention, the caller first evaluates the function to a normal form, from which it can read the number and kinds of arguments the function expects, and then it calls the function body with the right arguments.
    [Show full text]
  • Stepping Ocaml
    Stepping OCaml TsukinoFurukawa YouyouCong KenichiAsai Ochanomizu University Tokyo, Japan {furukawa.tsukino, so.yuyu, asai}@is.ocha.ac.jp Steppers, which display all the reduction steps of a given program, are a novice-friendly tool for un- derstanding program behavior. Unfortunately, steppers are not as popular as they ought to be; indeed, the tool is only available in the pedagogical languages of the DrRacket programming environment. We present a stepper for a practical fragment of OCaml. Similarly to the DrRacket stepper, we keep track of evaluation contexts in order to reconstruct the whole program at each reduction step. The difference is that we support effectful constructs, such as exception handling and printing primitives, allowing the stepper to assist a wider range of users. In this paper, we describe the implementationof the stepper, share the feedback from our students, and show an attempt at assessing the educational impact of our stepper. 1 Introduction Programmers spend a considerable amount of time and effort on debugging. In particular, novice pro- grammers may find this process extremely painful, since existing debuggers are usually not friendly enough to beginners. To use a debugger, we have to first learn what kinds of commands are available, and figure out which would be useful for the current purpose. It would be even harder to use the com- mand in a meaningful manner: for instance, to spot the source of an unexpected behavior, we must be able to find the right places to insert breakpoints, which requires some programming experience. Then, is there any debugging tool that is accessible to first-day programmers? In our view, the algebraic stepper [1] of DrRacket, a pedagogical programming environment for the Racket language, serves as such a tool.
    [Show full text]
  • A Practical Introduction to Python Programming
    A Practical Introduction to Python Programming Brian Heinold Department of Mathematics and Computer Science Mount St. Mary’s University ii ©2012 Brian Heinold Licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported Li- cense Contents I Basics1 1 Getting Started 3 1.1 Installing Python..............................................3 1.2 IDLE......................................................3 1.3 A first program...............................................4 1.4 Typing things in...............................................5 1.5 Getting input.................................................6 1.6 Printing....................................................6 1.7 Variables...................................................7 1.8 Exercises...................................................9 2 For loops 11 2.1 Examples................................................... 11 2.2 The loop variable.............................................. 13 2.3 The range function............................................ 13 2.4 A Trickier Example............................................. 14 2.5 Exercises................................................... 15 3 Numbers 19 3.1 Integers and Decimal Numbers.................................... 19 3.2 Math Operators............................................... 19 3.3 Order of operations............................................ 21 3.4 Random numbers............................................. 21 3.5 Math functions............................................... 21 3.6 Getting
    [Show full text]
  • Making a Faster Curry with Extensional Types
    Making a Faster Curry with Extensional Types Paul Downen Simon Peyton Jones Zachary Sullivan Microsoft Research Zena M. Ariola Cambridge, UK University of Oregon [email protected] Eugene, Oregon, USA [email protected] [email protected] [email protected] Abstract 1 Introduction Curried functions apparently take one argument at a time, Consider these two function definitions: which is slow. So optimizing compilers for higher-order lan- guages invariably have some mechanism for working around f1 = λx: let z = h x x in λy:e y z currying by passing several arguments at once, as many as f = λx:λy: let z = h x x in e y z the function can handle, which is known as its arity. But 2 such mechanisms are often ad-hoc, and do not work at all in higher-order functions. We show how extensional, call- It is highly desirable for an optimizing compiler to η ex- by-name functions have the correct behavior for directly pand f1 into f2. The function f1 takes only a single argu- expressing the arity of curried functions. And these exten- ment before returning a heap-allocated function closure; sional functions can stand side-by-side with functions native then that closure must subsequently be called by passing the to practical programming languages, which do not use call- second argument. In contrast, f2 can take both arguments by-name evaluation. Integrating call-by-name with other at once, without constructing an intermediate closure, and evaluation strategies in the same intermediate language ex- this can make a huge difference to run-time performance in presses the arity of a function in its type and gives a princi- practice [Marlow and Peyton Jones 2004].
    [Show full text]
  • Advanced Tcl E D
    PART II I I . A d v a n c Advanced Tcl e d T c l Part II describes advanced programming techniques that support sophisticated applications. The Tcl interfaces remain simple, so you can quickly construct pow- erful applications. Chapter 10 describes eval, which lets you create Tcl programs on the fly. There are tricks with using eval correctly, and a few rules of thumb to make your life easier. Chapter 11 describes regular expressions. This is the most powerful string processing facility in Tcl. This chapter includes a cookbook of useful regular expressions. Chapter 12 describes the library and package facility used to organize your code into reusable modules. Chapter 13 describes introspection and debugging. Introspection provides information about the state of the Tcl interpreter. Chapter 14 describes namespaces that partition the global scope for vari- ables and procedures. Namespaces help you structure large Tcl applications. Chapter 15 describes the features that support Internationalization, includ- ing Unicode, other character set encodings, and message catalogs. Chapter 16 describes event-driven I/O programming. This lets you run pro- cess pipelines in the background. It is also very useful with network socket pro- gramming, which is the topic of Chapter 17. Chapter 18 describes TclHttpd, a Web server built entirely in Tcl. You can build applications on top of TclHttpd, or integrate the server into existing appli- cations to give them a web interface. TclHttpd also supports regular Web sites. Chapter 19 describes Safe-Tcl and using multiple Tcl interpreters. You can create multiple Tcl interpreters for your application. If an interpreter is safe, then you can grant it restricted functionality.
    [Show full text]
  • CSE341, Fall 2011, Lecture 17 Summary
    CSE341, Fall 2011, Lecture 17 Summary Standard Disclaimer: This lecture summary is not necessarily a complete substitute for attending class, reading the associated code, etc. It is designed to be a useful resource for students who attended class and are later reviewing the material. This lecture covers three topics that are all directly relevant to the upcoming homework assignment in which you will use Racket to write an interpreter for a small programming language: • Racket's structs for defining new (dynamic) types in Racket and contrasting them with manually tagged lists [This material was actually covered in the \Monday section" and only briefly reviewed in the other materials for this lecture.] • How a programming language can be implemented in another programming language, including key stages/concepts like parsing, compilation, interpretation, and variants thereof • How to implement higher-order functions and closures by explicitly managing and storing environments at run-time One-of types with lists and dynamic typing In ML, we studied one-of types (in the form of datatype bindings) almost from the beginning, since they were essential for defining many kinds of data. Racket's dynamic typing makes the issue less central since we can simply use primitives like booleans and cons cells to build anything we want. Building lists and trees out of dynamically typed pairs is particularly straightforward. However, the concept of one-of types is still prevalent. For example, a list is null or a cons cell where there cdr holds a list. Moreover, Racket extends its Scheme predecessor with something very similar to ML's constructors | a special form called struct | and it is instructive to contrast the two language's approaches.
    [Show full text]
  • Specialising Dynamic Techniques for Implementing the Ruby Programming Language
    SPECIALISING DYNAMIC TECHNIQUES FOR IMPLEMENTING THE RUBY PROGRAMMING LANGUAGE A thesis submitted to the University of Manchester for the degree of Doctor of Philosophy in the Faculty of Engineering and Physical Sciences 2015 By Chris Seaton School of Computer Science This published copy of the thesis contains a couple of minor typographical corrections from the version deposited in the University of Manchester Library. [email protected] chrisseaton.com/phd 2 Contents List of Listings7 List of Tables9 List of Figures 11 Abstract 15 Declaration 17 Copyright 19 Acknowledgements 21 1 Introduction 23 1.1 Dynamic Programming Languages.................. 23 1.2 Idiomatic Ruby............................ 25 1.3 Research Questions.......................... 27 1.4 Implementation Work......................... 27 1.5 Contributions............................. 28 1.6 Publications.............................. 29 1.7 Thesis Structure............................ 31 2 Characteristics of Dynamic Languages 35 2.1 Ruby.................................. 35 2.2 Ruby on Rails............................. 36 2.3 Case Study: Idiomatic Ruby..................... 37 2.4 Summary............................... 49 3 3 Implementation of Dynamic Languages 51 3.1 Foundational Techniques....................... 51 3.2 Applied Techniques.......................... 59 3.3 Implementations of Ruby....................... 65 3.4 Parallelism and Concurrency..................... 72 3.5 Summary............................... 73 4 Evaluation Methodology 75 4.1 Evaluation Philosophy
    [Show full text]
  • Language Design and Implementation Using Ruby and the Interpreter Pattern
    Language Design and Implementation using Ruby and the Interpreter Pattern Ariel Ortiz Information Technology Department Tecnológico de Monterrey, Campus Estado de México Atizapán de Zaragoza, Estado de México, Mexico. 52926 [email protected] ABSTRACT to explore different language concepts and programming styles. In this paper, the S-expression Interpreter Framework (SIF) is Section 4 describes briefly how the SIF has been used in class. presented as a tool for teaching language design and The conclusions are found in section 5. implementation. The SIF is based on the interpreter design pattern and is written in the Ruby programming language. Its core is quite 1.1 S-Expressions S-expressions (symbolic expressions) are a parenthesized prefix small, but it can be easily extended by adding primitive notation, mainly used in the Lisp family languages. They were procedures and special forms. The SIF can be used to demonstrate selected as the framework’s source language notation because advanced language concepts (variable scopes, continuations, etc.) they offer important advantages: as well as different programming styles (functional, imperative, and object oriented). • Both code and data can be easily represented. Adding a new language construct is pretty straightforward. • Categories and Subject Descriptors The number of program elements is minimal, thus a parser is − relatively easy to write. D.3.4 [Programming Languages]: Processors interpreters, • run-time environments. The arity of operators doesn’t need to be fixed, so, for example, the + operator can be defined to accept any number of arguments. General Terms • There are no problems regarding operator precedence or Design, Languages. associativity, because every operator has its own pair of parentheses.
    [Show full text]
  • Functional Programming Lecture 1: Introduction
    Functional Programming Lecture 4: Closures and lazy evaluation Viliam Lisý Artificial Intelligence Center Department of Computer Science FEE, Czech Technical University in Prague [email protected] Last lecture • Functions with variable number of arguments • Higher order functions – map, foldr, foldl, filter, compose Binding scopes A portion of the source code where a value is bound to a given name • Lexical scope – functions use bindings available where defined • Dynamic scope – functions use bindings available where executed Ben Wood's slides Eval Eval is a function defined in the top level context (define x 5) (define (f y) (eval '(+ x y))) Fails because of lexical scoping Otherwise the following would be problematic (define (eval x) (eval-expanded (macro-expand x))) Eval in R5RS (eval expression environment) Where the environment is one of (interaction-environment) (global defines) (scheme-report-environment x) (before defines) (null-environment x) (only special forms) None of them allows seeing local bindings Cons (define (my-cons x y) (lambda (m) (m x y))) (define (my-car p) (p (lambda (x y) x))) (define (my-cdr s) (p (lambda (x y) y))) Currying Transforms function to allow partial application not curried 푓: 퐴 × 퐵 → 퐶 curried 푓: 퐴 → (퐵 → 퐶) not curried 푓: 퐴 × 퐵 × 퐶 → 퐷 curried 푓: 퐴 → (퐵 → 퐶 → 퐷 ) Picture: Haskell Curry from Wikipedia Currying (define (my-curry1 f) (lambda args (lambda rest (apply f (append args rest))))) (define (my-curry f) (define (c-wrap f stored-args) (lambda args (let ((all (append stored-args args)))
    [Show full text]
  • Records in Haskell
    Push/enter vs eval/apply Simon Marlow Simon Peyton Jones The question Consider the call (f x y). We can either Evaluate f, and then apply it to its arguments, or Push x and y, and enter f Both admit fully-general tail calls Which is better? Push/enter for (f x y) Stack of "pending arguments" Push y, x onto stack, enter (jump to) f f knows its own arity (say 1). It checks there is at least one argument on the stack. Grabs that argument, and executes its body, then enters its result (presumably a function) which consumes y Eval/apply for (f x y) Caller evaluates f, inspects result, which must be a function. Extracts arity from the function value. (Say it is 1.) Calls fn passing one arg (exactly what it is expecting) Inspects result, which must be a function. Extracts its arity... etc Known functions Often f is a known function let f x y = ... in ...(f 3 5).... In this case, we know f's arity statically; just load the arguments into registers and call f. This "known function" optimisation applies whether we are using push/enter or eval/apply So we only consider unknown calls from now on. Uncurried functions If f is an uncurried function: f :: (Int,Int) -> Int ....f (3,4)... Then a call site must supply exactly the right number of args So matching args expected by function with args supplied by call site is easier (=1). But we want efficient curried functions too And, in a lazy setting, can't do an efficient n- arg call for an unknown function, because it might not be strict.
    [Show full text]
  • Deriving Practical Implementations of First-Class Functions
    Deriving Practical Implementations of First-Class Functions ZACHARY J. SULLIVAN This document explores the ways in which a practical implementation is derived from Church’s calculus of _-conversion, i.e. the core of functional programming languages today like Haskell, Ocaml, Racket, and even Javascript. Despite the implementations being based on the calculus, the languages and their semantics that are used in compilers today vary greatly from their original inspiration. I show how incremental changes to Church’s semantics yield the intermediate theories of explicit substitutions, operational semantics, abstract machines, and compilation through intermediate languages, e.g. CPS, on the way to what is seen in modern functional language implementations. Throughout, a particular focus is given to the effect of evaluation strategy which separates Haskell and its implementation from the other languages listed above. 1 INTRODUCTION The gap between theory and practice can be a large one, both in terms of the gap in time between the creation of a theory and its implementation and the appearance of ideas in one versus the other. The difference in time between the presentation of Church’s _-calculus—i.e. the core of functional programming languages today like Haskell, Ocaml, Racket—and its mechanization was a period of 20 years. In the following 60 years till now, there has been further improvements in both the efficiency of the implementation, but also in reasoning about how implementations are related to the original calculus. To compute in the calculus is flexible (even non-deterministic), but real machines are both more complex and require restrictions on the allowable computation to be tractable.
    [Show full text]
  • The Ruby Intermediate Language ∗
    The Ruby Intermediate Language ∗ Michael Furr Jong-hoon (David) An Jeffrey S. Foster Michael Hicks University of Maryland, College Park ffurr,davidan,jfoster,[email protected] Abstract syntax that is almost ambiguous, and a semantics that includes Ruby is a popular, dynamic scripting language that aims to “feel a significant amount of special case, implicit behavior. While the natural to programmers” and give users the “freedom to choose” resulting language is arguably easy to use, its complex syntax and among many different ways of doing the same thing. While this ar- semantics make it hard to write tools that work with Ruby source guably makes programming in Ruby easier, it makes it hard to build code. analysis and transformation tools that operate on Ruby source code. In this paper, we describe the Ruby Intermediate Language In this paper, we present the Ruby Intermediate Language (RIL), (RIL), an intermediate language designed to make it easy to ex- a Ruby front-end and intermediate representation that addresses tend, analyze, and transform Ruby source code. As far as we are these challenges. RIL includes an extensible GLR parser for Ruby, aware, RIL is the only Ruby front-end designed with these goals in and an automatic translation into an easy-to-analyze intermediate mind. RIL provides four main advantages for working with Ruby form. This translation eliminates redundant language constructs, code. First, RIL’s parser is completely separated from the Ruby in- unravels the often subtle ordering among side effecting operations, terpreter, and is defined using a Generalized LR (GLR) grammar, and makes implicit interpreter operations explicit.
    [Show full text]