Skip to content

Commit 84893ac

Browse files
authored
Merge pull request #148 from chr1st0scli/streams
Support infinite streams.
2 parents 92850d6 + 1b572b6 commit 84893ac

33 files changed

+740
-7
lines changed

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,23 @@ Semantic versioning is followed.
1616

1717
### Removed
1818

19+
## [1.4.0] - 2025-02-16
20+
21+
### Added
22+
- `delay` special form for supporting delayed expressions.
23+
- `force` library procedure to force the evaluation of a delayed expression.
24+
- `cons-stream` derived expression for creating infinite streams.
25+
- `cdr-stream` library procedure to enable walking through a stream.
26+
- `make-range-stream` library procedure to make a numerical range stream.
27+
- `map-stream` library procedure that projects elements of a stream.
28+
- `filter-stream` library procedure that filters elements of a stream.
29+
30+
### Fixed
31+
32+
### Changed
33+
34+
### Removed
35+
1936
## [1.3.0] - 2024-06-30
2037

2138
### Added

RainLisp/AbstractSyntaxTree/Delay.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using RainLisp.Evaluation;
2+
using RainLisp.Evaluation.Results;
3+
4+
namespace RainLisp.AbstractSyntaxTree
5+
{
6+
/// <summary>
7+
/// Delayed expression in the abstract syntax tree.
8+
/// </summary>
9+
public class Delay : Expression
10+
{
11+
/// <summary>
12+
/// Initializes a new instance of the <see cref="Delay"/> class.
13+
/// </summary>
14+
/// <param name="delayed">The expression whose evaluation is meant to be delayed.</param>
15+
public Delay(Expression delayed)
16+
=> Delayed = delayed;
17+
18+
/// <summary>
19+
/// Gets ot sets the expression whose evaluation is meant to be delayed.
20+
/// </summary>
21+
public Expression Delayed { get; init; }
22+
23+
/// <summary>
24+
/// Evaluates the delay expression and returns the result.
25+
/// </summary>
26+
/// <param name="visitor">The visitor that implements the evaluation.</param>
27+
/// <param name="environment">The environment the evaluation occurs in.</param>
28+
/// <returns>The result of the evaluation.</returns>
29+
public override EvaluationResult AcceptVisitor(IEvaluatorVisitor visitor, IEvaluationEnvironment environment)
30+
=> visitor.EvaluateDelay(this, environment);
31+
}
32+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using RainLisp.AbstractSyntaxTree;
2+
3+
namespace RainLisp.DerivedExpressions
4+
{
5+
/// <summary>
6+
/// Cons stream expression as described in the syntax grammar.
7+
/// </summary>
8+
public class ConsStream
9+
{
10+
/// <summary>
11+
/// Initializes a new instance of the <see cref="ConsStream"/> class.
12+
/// </summary>
13+
/// <param name="first">The first constituent expression.</param>
14+
/// <param name="second">The second constituent expression.</param>
15+
public ConsStream(Expression first, Expression second)
16+
{
17+
First = first;
18+
Second = second;
19+
}
20+
21+
/// <summary>
22+
/// Gets or sets the first constituent expression.
23+
/// </summary>
24+
public Expression First { get; init; }
25+
26+
/// <summary>
27+
/// Gets or sets the second constituent expression.
28+
/// </summary>
29+
public Expression Second { get; init; }
30+
}
31+
}

RainLisp/DerivedExpressions/Transformations.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using RainLisp.AbstractSyntaxTree;
2+
using RainLisp.Grammar;
23

34
namespace RainLisp.DerivedExpressions
45
{
@@ -96,6 +97,15 @@ public static Expression ToIf(this Or or)
9697
return AndOrToNestedIf(or.Expressions, CreateIfForOr);
9798
}
9899

100+
/// <summary>
101+
/// Converts a cons stream expression to an application of the cons primitive procedure on the first and the delayed second
102+
/// constituent parts of the cons stream in the abstract syntax tree.
103+
/// </summary>
104+
/// <param name="consStream">The cons stream to convert.</param>
105+
/// <returns>An application of the cons primitive procedure on the first and the delayed second expressions.</returns>
106+
public static Application ToConsOnFirstAndDelayedSecond(this ConsStream consStream)
107+
=> new Application(new Identifier(Primitives.CONS), new[] { consStream.First, new Delay(consStream.Second) });
108+
99109
private static Expression CreateIfForAnd(Expression expression, Expression nestedExpression)
100110
// The current and's operand becomes the if's predicate, the rest operands go inside
101111
// the consequent as a a nested expression and the alternative is always false.

RainLisp/Docs/common-libraries.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# Common Libraries
2+
3+
Common libraries are procedures that are defined in the language itself.
4+
25
## List Operations
36
- [append](common-libraries/append.md)
47
- [filter](common-libraries/filter.md)
@@ -10,6 +13,13 @@
1013
- [reduce](common-libraries/reduce.md)
1114
- [reverse](common-libraries/reverse.md)
1215

16+
## Stream Operations
17+
- [cdr-stream](common-libraries/cdr-stream.md)
18+
- [filter-stream](common-libraries/filter-stream.md)
19+
- [force](common-libraries/force.md)
20+
- [make-range-stream](common-libraries/make-range-stream.md)
21+
- [map-stream](common-libraries/map-stream.md)
22+
1323
## car & cdr Flavors
1424
- [cddr](common-libraries/cddr.md)
1525
- [cadr](common-libraries/cadr.md)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# cdr-stream
2+
```scheme
3+
(define (cdr-stream stream)
4+
(force (cdr stream)))
5+
```
6+
Returns the rest of a stream, i.e. its next element by forcing its promise to be fulfilled and a promise for the rest of it.
7+
8+
## Example
9+
```scheme
10+
(cdr-stream (make-range-stream 1 8000000))
11+
```
12+
-> *(2 . [UserProcedure] Parameters: 0)*
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# filter-stream
2+
```scheme
3+
(define (filter-stream predicate stream)
4+
(cond ((null? stream) nil)
5+
((predicate (car stream))
6+
(cons-stream (car stream) (filter-stream predicate (cdr-stream stream))))
7+
(else (filter-stream predicate (cdr-stream stream)))))
8+
```
9+
Returns a new stream containing the first element of a stream that satisfies a condition and a promise for the rest of them.
10+
11+
> *predicate* is a procedure accepting a single argument (each element of *stream* at a time) and its result is evaluated as a boolean.
12+
13+
> *stream* is the stream to filter.
14+
15+
## Examples
16+
```scheme
17+
; Filter the even numbers of a stream.
18+
; It returns the first even number and a promise to filter the rest of them.
19+
(filter-stream
20+
(lambda (n) (= 0 (% n 2)))
21+
(make-range-stream 1 8))
22+
```
23+
-> *(2 . [UserProcedure] Parameters: 0)*
24+
25+
```scheme
26+
; Create a stream of evens.
27+
(define evens
28+
(filter-stream
29+
(lambda (n) (= 0 (% n 2)))
30+
(make-range-stream 1 8)))
31+
32+
; Find the second even.
33+
(car (cdr-stream evens))
34+
```
35+
-> *4*
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# force
2+
```scheme
3+
(define (force proc)
4+
(proc))
5+
```
6+
Executes a procedure and returns its result. Typically the procedure is a promise, i.e. a delayed expression
7+
that results from [delay](../special-forms-derived-expressions/delay.md).
8+
9+
> *proc* is the procedure to execute, typically a promise to evaluate an expression.
10+
11+
## Example
12+
```scheme
13+
(force (delay 4))
14+
```
15+
-> *4*
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# make-range-stream
2+
```scheme
3+
(define (make-range-stream start end)
4+
(if (> start end)
5+
nil
6+
(cons-stream start (make-range-stream (+ start 1) end))))
7+
```
8+
Returns a new stream of numeric values ranging from *start* until *end*. The first numeric value is evaluated and the evaluation of the rest is deferred,
9+
i.e. delayed until explicitly requested. If *start* is greater than *end*, [nil](../primitives/nil.md) is returned.
10+
11+
> *start* is the first value of the stream which is immediately evaluated.
12+
13+
> *end* is the last value of the stream.
14+
15+
## Example
16+
```scheme
17+
; Returns a stream made of the first element and a promise to fulfill the rest of it.
18+
(make-range-stream 7 5000000)
19+
```
20+
-> *(7 . [UserProcedure] Parameters: 0)*
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# map-stream
2+
```scheme
3+
(define (map-stream proc stream)
4+
(if (null? stream)
5+
nil
6+
(cons-stream (proc (car stream)) (map-stream proc (cdr-stream stream)))))
7+
```
8+
Returns a new stream with the projection of the first element and a promise for the projection of the rest of them.
9+
10+
> *proc* is a procedure accepting a single argument (each element of *stream* at a time) and returning a projection.
11+
12+
> *stream* is the delayed list whose each element is mapped to another one on demand.
13+
14+
## Examples
15+
```scheme
16+
; Multiply each number in a stream by 10.
17+
; It returns the projected first element and a promise to project the rest of them.
18+
(map-stream
19+
(lambda (n) (* n 10))
20+
(make-range-stream 1 5))
21+
```
22+
-> *(10 . [UserProcedure] Parameters: 0)*
23+
24+
```scheme
25+
; Create a stream of multiples of 10.
26+
(define projected-stream
27+
(map-stream
28+
(lambda (n) (* n 10))
29+
(make-range-stream 1 5)))
30+
31+
; Project the second element.
32+
(car (cdr-stream projected-stream))
33+
```
34+
-> *20*

RainLisp/Docs/primitives.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# Primitives
2+
3+
Primitives are procedures or semantics that require some kind of support from the underlying platform.
4+
25
- [+](primitives/plus.md)
36
- [-](primitives/minus.md)
47
- [*](primitives/multiply.md)

RainLisp/Docs/special-forms-derived-expressions.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ syntactic sugar, i.e. reserved keywords that are transformed to equivalent speci
66
- [and](special-forms-derived-expressions/and.md)
77
- [begin](special-forms-derived-expressions/begin.md)
88
- [cond](special-forms-derived-expressions/cond.md)
9+
- [cons-stream](special-forms-derived-expressions/cons-stream.md)
910
- [define](special-forms-derived-expressions/define.md)
11+
- [delay](special-forms-derived-expressions/delay.md)
1012
- [if](special-forms-derived-expressions/if.md)
1113
- [lambda](special-forms-derived-expressions/lambda.md)
1214
- [let](special-forms-derived-expressions/let.md)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# cons-stream
2+
A derived expression for constructing a stream, i.e. a pair of an immediately evaluated expression and a delayed one.
3+
```
4+
(cons-stream expression expression)
5+
```
6+
7+
The first expression is evaluated immediately and the second one is delayed. The result of the `cons-stream` expression
8+
is a pair made of the value of the first expression and a procedure which evaluates the second one when called, i.e. a promise.
9+
10+
## Examples
11+
```scheme
12+
(cons-stream 1 2)
13+
```
14+
-> *(1 . [UserProcedure] Parameters: 0)*
15+
16+
```scheme
17+
; Assign the procedure to a variable and call it later.
18+
(define delayed (cdr (cons-stream 1 2)))
19+
(delayed)
20+
```
21+
-> *2*
22+
23+
```scheme
24+
; Use the force library procedure to force the evaluation.
25+
(force (cdr (cons-stream 1 2)))
26+
```
27+
-> *2*
28+
29+
```scheme
30+
; Use the cdr-stream library procedure to force the evaluation.
31+
(cdr-stream (cons-stream 1 2))
32+
```
33+
-> *2*
34+
35+
## Remarks
36+
> Note that `(cons-stream a b)` is syntactic sugar for `(cons a (delay b))`.
37+
38+
> Note that the procedure created for the delayed expression is a memoized one. I.e. the expression is evaluated once
39+
when needed and subsequent calls will simply return the previously calculated value.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# delay
2+
A special form that delays the evaluation of the given expression, effectively creating a promise to evaluate it in the future.
3+
The evaluation result is a procedure which evaluates the expression when called.
4+
```
5+
(delay expression)
6+
```
7+
8+
## Examples
9+
```scheme
10+
; Delay displaying "Hello World!".
11+
(delay (display "Hello World!"))
12+
```
13+
-> *[UserProcedure] Parameters: 0*
14+
15+
```scheme
16+
; Assign the procedure to a variable and call it later.
17+
(define delayed (delay (display "Hello World!")))
18+
(delayed)
19+
```
20+
-> *Hello World!*
21+
22+
```scheme
23+
; Use the force library procedure to force the evaluation.
24+
(force (delay (display "Hello World!")))
25+
```
26+
-> *Hello World!*
27+
28+
## Remarks
29+
30+
> `(delay a)` is equivalent to `(lambda() a)`. So, it needs to be a special form; otherwise `(delay a)`
31+
would cause `a` to be evaluated before calling `delay` on it, due to the language's applicative order of evaluation
32+
which dictates that arguments are evaluated first before applying a procedure on them.
33+
34+
> Note that the procedure created for the delayed expression is a memoized one. I.e. the expression is evaluated once
35+
when needed and subsequent calls will simply return the previously calculated value.

RainLisp/Evaluation/EvaluatorVisitor.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,21 @@ public EvaluationResult EvaluateBody(Body body, IEvaluationEnvironment environme
192192
return EvaluateSequence(body.Expressions, environment);
193193
}
194194

195+
/// <summary>
196+
/// Returns the result of evaluating a delay.
197+
/// </summary>
198+
/// <param name="delay">The delay to evaluate.</param>
199+
/// <param name="environment">The environment which the evaluation occurs in.</param>
200+
/// <returns>The result of the evaluation.</returns>
201+
public EvaluationResult EvaluateDelay(Delay delay, IEvaluationEnvironment environment)
202+
{
203+
// An expression is delayed by simply putting it in a procedure body that will be called later.
204+
var body = new Body(null, new List<Expression> { delay.Delayed });
205+
206+
// A memoized user procedure caches the result of its first evaluation.
207+
return new MemoizedUserProcedure(null, body, environment);
208+
}
209+
195210
/// <summary>
196211
/// Returns the result of evaluating a program. The evaluation occurs lazily on a per request basis, while the return value is being enumerated.
197212
/// </summary>

RainLisp/Evaluation/IEvaluatorVisitor.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,14 @@ public interface IEvaluatorVisitor
110110
/// <exception cref="EvaluationException">An error occurs during the evaluation of the <paramref name="body"/>.</exception>
111111
EvaluationResult EvaluateBody(Body body, IEvaluationEnvironment environment);
112112

113+
/// <summary>
114+
/// Returns the result of evaluating a delay.
115+
/// </summary>
116+
/// <param name="delay">The delay to evaluate.</param>
117+
/// <param name="environment">The environment which the evaluation occurs in.</param>
118+
/// <returns>The result of the evaluation.</returns>
119+
EvaluationResult EvaluateDelay(Delay delay, IEvaluationEnvironment environment);
120+
113121
/// <summary>
114122
/// Returns the result of evaluating a program. The evaluation occurs lazily on a per request basis, while the return value is being enumerated.
115123
/// </summary>

0 commit comments

Comments
 (0)