Skip to content

Create format-basics concept & concept exercise. #537

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Nov 24, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions concepts/format-basics/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"blurb": "Basic information about formatting output.",
"authors": ["verdammelt"]
}
28 changes: 28 additions & 0 deletions concepts/format-basics/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# About

The Common Lisp Printer is the facility for outputting data to streams. One common function in this facility is `format`.

`format` takes two required parameters: a stream to write to and a control string. The contents of the control string determine how any further arguments are used.

The first parameter has two special values: `t` and `nil`. `t` indicates that the stream bound to `*standard-output*` should be used and `nil` indicates that a string should be created and returned. `format` evaluates to `nil` unless the stream parameter is `nil`.

The control string can contain format directives which are signified by a `~` followed by a character (the case of the character is not important). The format directive will define if any arguments are consumed and how they are formatted to the stream.

The important basic format directives are:

* `~A` - _aesthetically_ format in a "human readable" way.
* `~S` - format in the _standard_ to be "machine readable".
* `~%` - emit a newline
* `~&` - emit a newline unless already at the beginning of a line.
* `~~` - emit a tilde.

Examples:

```lisp
(format nil "The list is: ~A~%" (list 1 2 3)) ; => "The list is: (1 2 3)
"
(format nil "(format t ~S)" "hello world") ; => "(format t \"hello world\""
(format nil "~&fresh~&lines") ;=> "fresh
lines"
(format nil "directives start with a ~~") ; => "directives start with a ~"
```
56 changes: 56 additions & 0 deletions concepts/format-basics/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Introduction

Common Lisp has a concept of The Printer which includes all functions for writing textural representations of Lisp data. One such function is `format` which allows the programmer to format the data as they want.

The abilities of `format` and the Lisp Printer in general is a large topic; here the basics of the use of `format` will be described.

## The FORMAT function

The function `format` takes at least two arguments: a stream to print to and control string defining what and how is to be printed.

Streams are a concept of their own and will be described elsewhere. But for now all one needs to know is that the printing is done to the stream and there are two special values: `t` and `nil` for this argument. `t` means to print to standard output (actually the stream to which the variable `*standard-output*` is bound). and `nil` means to create and string and return it.

Note that `format` will always evaluate to `nil` except in that latter case where `nil` is specified as the stream. This is a common way to do simply string interpolation or construction.

```lisp
(format nil "hello world") ; => "hello world"
(format t "hello world") ; => NIL (but "hello world" is printed to standard output)
```

## Format Directives

The control string can contain "format directives" which give instructions to `format` about what to print and how to interpret any other parameters to `format`. In a sense the control string is a program which `format` will interpret and run. All directives start with `~` and are followed by a character (_e.g._ `~A`, `~D`, `~&`). The case of the character does not matter.

Most format directives simply interpolate an argument to `format` into the output. This is said to "consume" the argument. Some directives do not consume arguments, or consume more than one. One directive even allows you to jump around in the argument list or skip arguments.

## Basic formatting

A very general purpose format directive is `~A`. This will print a 'human readable' version of the data.

```lisp
(format nil "Value = ~a" 10) ; => "10"
(format nil "hello ~a" "world") ; => "hello world"
(format nil "The list is: ~A" (list 1 2 3)) ; => "The list is: (1 2 3)"
```

Another useful format directive is `~S`. This will print a 'machine readable' version of the data. This can be very useful if you want to be able to interpret the output created as Lisp data. For many things it will look the same as `~A` but note that for strings for example the output will include the quotation marks (escaped when included in another string) so that the value could be read back in as the string parameter.

```lisp
(format nil "~s" 10) ; => "10"
(format nil "~s" "hello world") ; => "\"hello world\""
(format nil "~s" (list 1 2 3)) ; => "(1 2 3)"
```

Two more very useful directives are `~%` and `~&`. The first outputs a newline character while the latter outputs a "fresh line". The difference is that `~%` always outputs a newline while `~&` will output one if not currently at the beginning of a line. Neither of these consume an argument.

```lisp
(format nil "~%new~%lines") ; => "
new
lines"
(format nil "~&new~&lines" ; => "new
lines"
```

Finally `~~` is a simple directive that will output a literal `~`.

18 changes: 18 additions & 0 deletions concepts/format-basics/links.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[
{
"url": "https://gigamonkeys.com/book/a-few-format-recipes.html#basic-formatting",
"description": "Information about basic format directives."
},
{
"url": "https://gigamonkeys.com/book/a-few-format-recipes.html#the-format-function",
"description": "Description of the format function."
},
{
"url": "https://gigamonkeys.com/book/a-few-format-recipes.html#format-directives",
"description": "Overview of format directives."
},
{
"url": "http://www.lispworks.com/documentation/HyperSpec/Body/22_c.htm",
"description": "Hyperspec chapter on formatting output."
}
]
32 changes: 25 additions & 7 deletions config.json
Original file line number Diff line number Diff line change
@@ -154,6 +154,14 @@
"keyword-parameters"
],
"status": "beta"
},
{
"slug": "reporting-for-duty",
"name": "Reporting for Duty",
"uuid": "c72ffe7d-eda5-41b0-b1ae-10c9db1550fa",
"concepts": ["format-basics"],
"prerequisites": ["strings", "functions"],
"status": "beta"
}
],
"practice": [
@@ -170,12 +178,17 @@
"slug": "two-fer",
"name": "Two Fer",
"uuid": "b80ab7d9-6152-4351-b405-07c2fb071962",
"practices": ["optional-parameters", "conditionals", "functions"],
"practices": [
"optional-parameters",
"conditionals",
"functions",
"format-basics"
],
"prerequisites": [
"expressions",
"functions",
"optional-parameters",
"conditionals"
"conditionals",
"format-basics"
],
"difficulty": 1,
"status": "beta"
@@ -248,17 +261,17 @@
"name": "Bob",
"uuid": "1e6c8365-775d-4a4f-8fc7-e376912d7912",
"difficulty": 1,
"practices": ["conditionals", "strings"],
"prerequisites": ["conditionals", "strings"],
"practices": ["conditionals", "strings", "format-basics"],
"prerequisites": ["conditionals", "strings", "format-basics"],
"status": "beta"
},
{
"slug": "twelve-days",
"name": "Twelve Days",
"uuid": "40ce6656-b8f5-4156-94b6-d634876215b2",
"difficulty": 4,
"practices": ["strings"],
"prerequisites": ["strings"],
"practices": ["format-basics"],
"prerequisites": ["format-basics"],
"status": "beta"
},
{
@@ -608,6 +621,11 @@
"slug": "floating-point-numbers",
"name": "Floating Point Numbers"
},
{
"uuid": "b4d57dc7-761d-4a53-818f-21aaa19e8db8",
"slug": "format-basics",
"name": "Format - Basics"
},
{
"uuid": "a7b463d3-4d7f-4b4a-8a47-28307219541d",
"slug": "functions",
18 changes: 18 additions & 0 deletions exercises/concept/reporting-for-duty/.docs/hints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Hints

- The `format` function will be needed for this exercise.

## 1. Formatting quarterly values

- `format` always returns `nil` unless the stream parameter is `nl`.
- `~A` can be used to format a value.

## 2. The Two-Quarter report

- Your previous function is perfect to create the two lines needed.
- `~%` and `~&` can be used to create newlines.

## 3. Human vs. Lisp Alien readable

- Your previous quarter formatting function is perfect to create the needed strings.
- `~S` can be used to format values in a readable form.
42 changes: 42 additions & 0 deletions exercises/concept/reporting-for-duty/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Instructions

Like just about everyone with a job Layla the Lisp Alien sometimes needs to make reports to let others know information. They ask if you could help them format data she has for these reports.

## 1. Formatting quarterly values

The first thing Layla needs is a function: `format-quarter-value` that takes two parameters: the calendar quarter and the value and formats it for a report. It should evaluate to a string.

```lisp
(format-quarter-value "last" 3.14) ; => "The value last quarter: 3.14"
(format-quarter-value "this" 0) ; => "The value this quarter: 0"
```

## 2. The Two-Quarter report

Layla thanks you for the `format-quarter-value` function and says it will be very useful in the *next* function that is needed.

The next report needs to show the quarter and value from 2 quarters printed on two lines.

Also the output needs to go to the stream provided as the first argument.

For example: `(format-two-quarters t "last" 3.14 "this" 0)` will produce the following on standard output:

```
The value last quarter: 3.14
The value this quarter: 0
```

Note the blank lines around it.

## 3. Human vs. Lisp Alien readable

Layla forgot to tell you that Lisp Aliens like to see values in a way that is readable as Lisp data. So they need a new function that could be read as lisp data (again to the specified stream):

```lisp
(format-two-quarters-for-reading nil "last" 3.14 "this" 0) ; =>
"(\"The value last quarter: 3.14\" \"The value this quarter: 0\")"
```

55 changes: 55 additions & 0 deletions exercises/concept/reporting-for-duty/.docs/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Introduction

Common Lisp has a concept of The Printer which includes all functions for writing textural representations of Lisp data. One such function is `format` which allows the programmer to format the data as they want.

The abilities of `format` and the Lisp Printer in general is a large topic; here the basics of the use of `format` will be described.

## The FORMAT function

The function `format` takes at least two arguments: a stream to print to and control string defining what and how is to be printed.

Streams are a concept of their own and will be described elsewhere. But for now all one needs to know is that the printing is done to the stream and there are two special values: `t` and `nil` for this argument. `t` means to print to standard output (actually the stream to which the variable `*standard-output*` is bound). and `nil` means to create and string and return it.

Note that `format` will always evaluate to `nil` except in that latter case where `nil` is specified as the stream. This is a common way to do simply string interpolation or construction.

```lisp
(format nil "hello world") ; => "hello world"
(format t "hello world") ; => NIL (but "hello world" is printed to standard output)
```

## Format Directives

The control string can contain "format directives" which give instructions to `format` about what to print and how to interpret any other parameters to `format`. In a sense the control string is a program which `format` will interpret and run. All directives start with `~` and are followed by a character (_e.g._ `~A`, `~D`, `~&`). The case of the character does not matter.

Most format directives simply interpolate an argument to `format` into the output. This is said to "consume" the argument. Some directives do not consume arguments, or consume more than one. One directive even allows you to jump around in the argument list or skip arguments.

## Basic formatting

A very general purpose format directive is `~A`. This will print a 'human readable' version of the data.

```lisp
(format nil "Value = ~a" 10) ; => "10"
(format nil "hello ~a" "world") ; => "hello world"
(format nil "The list is: ~A" (list 1 2 3)) ; => "The list is: (1 2 3)"
```

Another useful format directive is `~S`. This will print a 'machine readable' version of the data. This can be very useful if you want to be able to interpret the output created as Lisp data. For many things it will look the same as `~A` but note that for strings for example the output will include the quotation marks (escaped when included in another string) so that the value could be read back in as the string parameter.

```lisp
(format nil "~s" 10) ; => "10"
(format nil "~s" "hello world") ; => "\"hello world\""
(format nil "~s" (list 1 2 3)) ; => "(1 2 3)"
```

Two more very useful directives are `~%` and `~&`. The first outputs a newline character while the latter outputs a "fresh line". The difference is that `~%` always outputs a newline while `~&` will output one if not currently at the beginning of a line. Neither of these consume an argument.

```lisp
(format nil "~%new~%lines") ; => "
new
lines"
(format nil "~&new~&lines" ; => "new
lines"
```

Finally `~~` is a simple directive that will output a literal `~`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Introduction

%{concept:format-basics}
10 changes: 10 additions & 0 deletions exercises/concept/reporting-for-duty/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"blurb": "Help Layla produce short reports",
"authors": ["verdammelt"],
"files": {
"solution": ["reporting-for-duty.lisp"],
"test": ["reporting-for-duty-test.lisp"],
"exemplar": [".meta/exemplar.lisp"]
},
"icon": "high-scores"
}
19 changes: 19 additions & 0 deletions exercises/concept/reporting-for-duty/.meta/exemplar.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
(defpackage :reporting-for-duty
(:use :cl)
(:export :format-quarter-value :format-two-quarters
:format-two-quarters-for-reading))

(in-package :reporting-for-duty)

(defun format-quarter-value (quarter value)
(format nil "The value ~A quarter: ~A" quarter value))

(defun format-two-quarters (stream quarter1 value1 quarter2 value2)
(format stream "~%~A~&~A~&"
(format-quarter-value quarter1 value1)
(format-quarter-value quarter2 value2)))

(defun format-two-quarters-for-reading (stream quarter1 value1 quarter2 value2)
(format stream "(~S ~S)"
(format-quarter-value quarter1 value1)
(format-quarter-value quarter2 value2)))
73 changes: 73 additions & 0 deletions exercises/concept/reporting-for-duty/reporting-for-duty-test.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
;; Ensures that reporting-for-duty.lisp and the testing library are always loaded
(eval-when (:compile-toplevel :load-toplevel :execute)
(load "reporting-for-duty")
(ql:quickload :fiveam))

;; Defines the testing package with symbols from reporting-for-duty and FiveAM in scope
;; The `run-tests` function is exported for use by both the user and test-runner
(defpackage :reporting-for-duty-test
(:use :cl :fiveam :reporting-for-duty)
(:export :run-tests))

;; Enter the testing package
(in-package :reporting-for-duty-test)

;; Define and enter a new FiveAM test-suite
(def-suite reporting-for-duty-suite)
(in-suite reporting-for-duty-suite)

(test format-quarter-value "report on the value for the given quarter"
(is (string= "The value next quarter: A"
(format-quarter-value "next" 'a)))
(is (string= "The value previous quarter: (1 2 3)"
(format-quarter-value "previous" '(1 2 3)))))

(test format-two-quarters-string "report on value of two quarters written to a string"
(is (string=
"
The value next quarter: (1 2 3)
The value previous quarter: A
"
(format-two-quarters nil "next" '(1 2 3) "previous" 'a))))

(test format-two-quarters-stream "report on value of two quarters written to a stream"
(is (string=
"
The value last quarter: 5
The value next quarter: -2
"
(with-output-to-string (stream)
(format-two-quarters stream "last" 5 "next" -2)))))

(test format-two-quarters-stdout "report on value of two quarters written to standard output"
(is (string=
"
The value penultimate quarter: 13
The value ultimate quarter: NIL
"
(with-output-to-string (stream)
(let ((*standard-output* stream))
(format-two-quarters stream "penultimate" 13 "ultimate" nil))))))

(test format-readable-string "produce a readable report to a string"
(is (string=
"(\"The value next quarter: (1 2 3)\" \"The value previous quarter: A\")"
(format-two-quarters-for-reading nil "next" '(1 2 3) "previous" 'a))))


(test format-readable-stream "produce a readable report to a stream"
(is (string=
"(\"The value last quarter: 5\" \"The value next quarter: -2\")"
(with-output-to-string (stream)
(format-two-quarters-for-reading stream "last" 5 "next" -2)))))

(test format-readable-stdout "produce a readable report to standard output"
(is (string=
"(\"The value penultimate quarter: 13\" \"The value ultimate quarter: NIL\")"
(with-output-to-string (stream)
(let ((*standard-output* stream))
(format-two-quarters-for-reading stream "penultimate" 13 "ultimate" nil))))))

(defun run-tests (&optional (test-or-suite 'reporting-for-duty-suite))
"Provides human readable results of test run. Default to entire suite."
(run! test-or-suite))
12 changes: 12 additions & 0 deletions exercises/concept/reporting-for-duty/reporting-for-duty.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
(defpackage :reporting-for-duty
(:use :cl)
(:export :format-quarter-value :format-two-quarters
:format-two-quarters-for-reading))

(in-package :reporting-for-duty)

;; Define format-quarter-value function.

;; Define format-two-quarters function.

;; Define format-two-quarters-for-reading function.