mirror of https://github.com/microsoft/schemy.git
Initial port of Schemy code base to Github
Migrate Schemy code base from Microsoft internal repository to Github. Schemy is a lightweight, embeddable Scheme-like language interpreter. It is open sourced under the MIT license (see LICENSE). For any comment/issue, please contact author kefei.lu@microsoft.com.pull/2/head
parent
155be4d717
commit
1e98c1b4c7
|
@ -1,288 +1,6 @@
|
||||||
## Ignore Visual Studio temporary files, build results, and
|
bin/
|
||||||
## files generated by popular Visual Studio add-ons.
|
obj/
|
||||||
##
|
|
||||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
|
||||||
|
|
||||||
# User-specific files
|
|
||||||
*.suo
|
|
||||||
*.user
|
|
||||||
*.userosscache
|
|
||||||
*.sln.docstates
|
|
||||||
|
|
||||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
|
||||||
*.userprefs
|
|
||||||
|
|
||||||
# Build results
|
|
||||||
[Dd]ebug/
|
|
||||||
[Dd]ebugPublic/
|
|
||||||
[Rr]elease/
|
|
||||||
[Rr]eleases/
|
|
||||||
x64/
|
|
||||||
x86/
|
|
||||||
bld/
|
|
||||||
[Bb]in/
|
|
||||||
[Oo]bj/
|
|
||||||
[Ll]og/
|
|
||||||
|
|
||||||
# Visual Studio 2015 cache/options directory
|
|
||||||
.vs/
|
.vs/
|
||||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
*.swp
|
||||||
#wwwroot/
|
|
||||||
|
|
||||||
# MSTest test Results
|
|
||||||
[Tt]est[Rr]esult*/
|
|
||||||
[Bb]uild[Ll]og.*
|
|
||||||
|
|
||||||
# NUNIT
|
|
||||||
*.VisualState.xml
|
|
||||||
TestResult.xml
|
|
||||||
|
|
||||||
# Build Results of an ATL Project
|
|
||||||
[Dd]ebugPS/
|
|
||||||
[Rr]eleasePS/
|
|
||||||
dlldata.c
|
|
||||||
|
|
||||||
# .NET Core
|
|
||||||
project.lock.json
|
|
||||||
project.fragment.lock.json
|
|
||||||
artifacts/
|
|
||||||
**/Properties/launchSettings.json
|
|
||||||
|
|
||||||
*_i.c
|
|
||||||
*_p.c
|
|
||||||
*_i.h
|
|
||||||
*.ilk
|
|
||||||
*.meta
|
|
||||||
*.obj
|
|
||||||
*.pch
|
|
||||||
*.pdb
|
|
||||||
*.pgc
|
|
||||||
*.pgd
|
|
||||||
*.rsp
|
|
||||||
*.sbr
|
|
||||||
*.tlb
|
|
||||||
*.tli
|
|
||||||
*.tlh
|
|
||||||
*.tmp
|
|
||||||
*.tmp_proj
|
|
||||||
*.log
|
|
||||||
*.vspscc
|
|
||||||
*.vssscc
|
|
||||||
.builds
|
|
||||||
*.pidb
|
|
||||||
*.svclog
|
|
||||||
*.scc
|
|
||||||
|
|
||||||
# Chutzpah Test files
|
|
||||||
_Chutzpah*
|
|
||||||
|
|
||||||
# Visual C++ cache files
|
|
||||||
ipch/
|
|
||||||
*.aps
|
|
||||||
*.ncb
|
|
||||||
*.opendb
|
|
||||||
*.opensdf
|
|
||||||
*.sdf
|
|
||||||
*.cachefile
|
|
||||||
*.VC.db
|
|
||||||
*.VC.VC.opendb
|
|
||||||
|
|
||||||
# Visual Studio profiler
|
|
||||||
*.psess
|
|
||||||
*.vsp
|
|
||||||
*.vspx
|
|
||||||
*.sap
|
|
||||||
|
|
||||||
# TFS 2012 Local Workspace
|
|
||||||
$tf/
|
|
||||||
|
|
||||||
# Guidance Automation Toolkit
|
|
||||||
*.gpState
|
|
||||||
|
|
||||||
# ReSharper is a .NET coding add-in
|
|
||||||
_ReSharper*/
|
|
||||||
*.[Rr]e[Ss]harper
|
|
||||||
*.DotSettings.user
|
|
||||||
|
|
||||||
# JustCode is a .NET coding add-in
|
|
||||||
.JustCode
|
|
||||||
|
|
||||||
# TeamCity is a build add-in
|
|
||||||
_TeamCity*
|
|
||||||
|
|
||||||
# DotCover is a Code Coverage Tool
|
|
||||||
*.dotCover
|
|
||||||
|
|
||||||
# Visual Studio code coverage results
|
|
||||||
*.coverage
|
|
||||||
*.coveragexml
|
|
||||||
|
|
||||||
# NCrunch
|
|
||||||
_NCrunch_*
|
|
||||||
.*crunch*.local.xml
|
|
||||||
nCrunchTemp_*
|
|
||||||
|
|
||||||
# MightyMoose
|
|
||||||
*.mm.*
|
|
||||||
AutoTest.Net/
|
|
||||||
|
|
||||||
# Web workbench (sass)
|
|
||||||
.sass-cache/
|
|
||||||
|
|
||||||
# Installshield output folder
|
|
||||||
[Ee]xpress/
|
|
||||||
|
|
||||||
# DocProject is a documentation generator add-in
|
|
||||||
DocProject/buildhelp/
|
|
||||||
DocProject/Help/*.HxT
|
|
||||||
DocProject/Help/*.HxC
|
|
||||||
DocProject/Help/*.hhc
|
|
||||||
DocProject/Help/*.hhk
|
|
||||||
DocProject/Help/*.hhp
|
|
||||||
DocProject/Help/Html2
|
|
||||||
DocProject/Help/html
|
|
||||||
|
|
||||||
# Click-Once directory
|
|
||||||
publish/
|
|
||||||
|
|
||||||
# Publish Web Output
|
|
||||||
*.[Pp]ublish.xml
|
|
||||||
*.azurePubxml
|
|
||||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
|
||||||
# but database connection strings (with potential passwords) will be unencrypted
|
|
||||||
*.pubxml
|
|
||||||
*.publishproj
|
|
||||||
|
|
||||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
|
||||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
|
||||||
# in these scripts will be unencrypted
|
|
||||||
PublishScripts/
|
|
||||||
|
|
||||||
# NuGet Packages
|
|
||||||
*.nupkg
|
|
||||||
# The packages folder can be ignored because of Package Restore
|
|
||||||
**/packages/*
|
|
||||||
# except build/, which is used as an MSBuild target.
|
|
||||||
!**/packages/build/
|
|
||||||
# Uncomment if necessary however generally it will be regenerated when needed
|
|
||||||
#!**/packages/repositories.config
|
|
||||||
# NuGet v3's project.json files produces more ignorable files
|
|
||||||
*.nuget.props
|
|
||||||
*.nuget.targets
|
|
||||||
|
|
||||||
# Microsoft Azure Build Output
|
|
||||||
csx/
|
|
||||||
*.build.csdef
|
|
||||||
|
|
||||||
# Microsoft Azure Emulator
|
|
||||||
ecf/
|
|
||||||
rcf/
|
|
||||||
|
|
||||||
# Windows Store app package directories and files
|
|
||||||
AppPackages/
|
|
||||||
BundleArtifacts/
|
|
||||||
Package.StoreAssociation.xml
|
|
||||||
_pkginfo.txt
|
|
||||||
|
|
||||||
# Visual Studio cache files
|
|
||||||
# files ending in .cache can be ignored
|
|
||||||
*.[Cc]ache
|
|
||||||
# but keep track of directories ending in .cache
|
|
||||||
!*.[Cc]ache/
|
|
||||||
|
|
||||||
# Others
|
|
||||||
ClientBin/
|
|
||||||
~$*
|
|
||||||
*~
|
*~
|
||||||
*.dbmdl
|
*.nupkg
|
||||||
*.dbproj.schemaview
|
|
||||||
*.jfm
|
|
||||||
*.pfx
|
|
||||||
*.publishsettings
|
|
||||||
orleans.codegen.cs
|
|
||||||
|
|
||||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
|
||||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
|
||||||
#bower_components/
|
|
||||||
|
|
||||||
# RIA/Silverlight projects
|
|
||||||
Generated_Code/
|
|
||||||
|
|
||||||
# Backup & report files from converting an old project file
|
|
||||||
# to a newer Visual Studio version. Backup files are not needed,
|
|
||||||
# because we have git ;-)
|
|
||||||
_UpgradeReport_Files/
|
|
||||||
Backup*/
|
|
||||||
UpgradeLog*.XML
|
|
||||||
UpgradeLog*.htm
|
|
||||||
|
|
||||||
# SQL Server files
|
|
||||||
*.mdf
|
|
||||||
*.ldf
|
|
||||||
*.ndf
|
|
||||||
|
|
||||||
# Business Intelligence projects
|
|
||||||
*.rdl.data
|
|
||||||
*.bim.layout
|
|
||||||
*.bim_*.settings
|
|
||||||
|
|
||||||
# Microsoft Fakes
|
|
||||||
FakesAssemblies/
|
|
||||||
|
|
||||||
# GhostDoc plugin setting file
|
|
||||||
*.GhostDoc.xml
|
|
||||||
|
|
||||||
# Node.js Tools for Visual Studio
|
|
||||||
.ntvs_analysis.dat
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
# Typescript v1 declaration files
|
|
||||||
typings/
|
|
||||||
|
|
||||||
# Visual Studio 6 build log
|
|
||||||
*.plg
|
|
||||||
|
|
||||||
# Visual Studio 6 workspace options file
|
|
||||||
*.opt
|
|
||||||
|
|
||||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
|
||||||
*.vbw
|
|
||||||
|
|
||||||
# Visual Studio LightSwitch build output
|
|
||||||
**/*.HTMLClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/GeneratedArtifacts
|
|
||||||
**/*.DesktopClient/ModelManifest.xml
|
|
||||||
**/*.Server/GeneratedArtifacts
|
|
||||||
**/*.Server/ModelManifest.xml
|
|
||||||
_Pvt_Extensions
|
|
||||||
|
|
||||||
# Paket dependency manager
|
|
||||||
.paket/paket.exe
|
|
||||||
paket-files/
|
|
||||||
|
|
||||||
# FAKE - F# Make
|
|
||||||
.fake/
|
|
||||||
|
|
||||||
# JetBrains Rider
|
|
||||||
.idea/
|
|
||||||
*.sln.iml
|
|
||||||
|
|
||||||
# CodeRush
|
|
||||||
.cr/
|
|
||||||
|
|
||||||
# Python Tools for Visual Studio (PTVS)
|
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
|
||||||
|
|
||||||
# Cake - Uncomment if you are using it
|
|
||||||
# tools/**
|
|
||||||
# !tools/packages.config
|
|
||||||
|
|
||||||
# Telerik's JustMock configuration file
|
|
||||||
*.jmconfig
|
|
||||||
|
|
||||||
# BizTalk build output
|
|
||||||
*.btp.cs
|
|
||||||
*.btm.cs
|
|
||||||
*.odx.cs
|
|
||||||
*.xsd.cs
|
|
||||||
|
|
201
README.md
201
README.md
|
@ -1,3 +1,197 @@
|
||||||
|
# Schemy
|
||||||
|
|
||||||
|
Schemy is a lightweight Scheme-like scripting language interpreter for
|
||||||
|
embedded use in .NET applications. It's built from scratch without any
|
||||||
|
external dependency. Its primary goal is to serve as a highly flexible
|
||||||
|
configuration language. Example scenarios are to describe computational
|
||||||
|
graph, workflow, or to represent some complex configuration.
|
||||||
|
|
||||||
|
Its design goals are:
|
||||||
|
|
||||||
|
* easy to embed and extend in .NET
|
||||||
|
* extensible in Scheme via macro expansion
|
||||||
|
* safe without the need of complicated AppDomain sandboxing. It's safe because
|
||||||
|
IO functions are not implemented.
|
||||||
|
* runs reasonably fast and low memory footprint
|
||||||
|
|
||||||
|
Non-goals:
|
||||||
|
|
||||||
|
* be highly optimized - it's designed to load configurations and not part of
|
||||||
|
any heavy computation, so being optimized is not the goal - e.g., there's no
|
||||||
|
JIT compiling, etc.
|
||||||
|
|
||||||
|
|
||||||
|
Schemy's implementation is inspired by Peter Norvig's [article on Lisp
|
||||||
|
interpreter][lispy], but is heavily adapted to .NET and engineered to be easily
|
||||||
|
extensible and embeddable in .NET applications.
|
||||||
|
|
||||||
|
|
||||||
|
## Scheme Features
|
||||||
|
|
||||||
|
It has most features that a language would support:
|
||||||
|
|
||||||
|
* number, boolean, string, list types
|
||||||
|
* varaible, function definition
|
||||||
|
* tail call optimization
|
||||||
|
* macro definition
|
||||||
|
* lexical scoping
|
||||||
|
|
||||||
|
|
||||||
|
Many Scheme features are not (yet) supported. Among those are:
|
||||||
|
|
||||||
|
* continuation (`call/cc`)
|
||||||
|
* use square brackets `[...]` in place of parenthesis `(...)`
|
||||||
|
|
||||||
|
|
||||||
|
## Embedding and Extending Schemy
|
||||||
|
|
||||||
|
Schemy is primarily designed to be embedded into a .NET application for
|
||||||
|
configuration or as a [shell-like interactive environment (REPL)](#repl). To
|
||||||
|
use Schemy, you can either:
|
||||||
|
|
||||||
|
1. Reference `schemy.dll`, or
|
||||||
|
2. Copy `src/schemy/*.cs` source code to include in your application. Since
|
||||||
|
Schemy code base is small. This approach is very feasible (don't forget to
|
||||||
|
also include the resource file `init.ss`).
|
||||||
|
|
||||||
|
|
||||||
|
The below sections describes how to embed and extend Schemy in .NET
|
||||||
|
applications and in Scheme scripts. For a comprehensive example, please refer
|
||||||
|
to [`src/examples/command_server`](src/examples/command_server).
|
||||||
|
|
||||||
|
|
||||||
|
### Extending Schemy in .NET
|
||||||
|
|
||||||
|
Schemy can be extended by feeding the interpreter symbols with predefined
|
||||||
|
.NET objects. Variables could be any .NET type. Procedures
|
||||||
|
must implement `ICallable`.
|
||||||
|
|
||||||
|
An example procedure implementation:
|
||||||
|
|
||||||
|
new NativeProcedure(args => args, "list");
|
||||||
|
|
||||||
|
This implements the Scheme procedure `list`, which converts its arguments
|
||||||
|
into a list:
|
||||||
|
|
||||||
|
schemy> (list 1 2 3 4)
|
||||||
|
(1 2 3 4)
|
||||||
|
|
||||||
|
To "register" extensions, one can pass them to the `Interpreter`'s
|
||||||
|
constructor:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
Interpreter.CreateSymbolTableDelegate extension = itpr => new Dictionary<Symbol, object>
|
||||||
|
{
|
||||||
|
{ Symbol.FromString("list"), new NativeProcedure(args => args, "list") },
|
||||||
|
};
|
||||||
|
|
||||||
|
var interpreter = new Interpreter(new[] { extension });
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Extending Schemy in Scheme
|
||||||
|
|
||||||
|
When launched, the interpreter tries to locate and load Scheme file `.init.ss`
|
||||||
|
in the same directory as the executing assembly. You can extend Schemy by
|
||||||
|
putting function, variable, macro definition inside this file.
|
||||||
|
|
||||||
|
|
||||||
|
#### Extending with functions
|
||||||
|
|
||||||
|
For example, this function implements the standard Scheme list reversion
|
||||||
|
function `reverse` (with proper tail call optimization):
|
||||||
|
|
||||||
|
```scheme
|
||||||
|
(define (reverse ls)
|
||||||
|
(define loop
|
||||||
|
(lambda (ls acc)
|
||||||
|
(if (null? ls) acc
|
||||||
|
(loop (cdr ls) (cons (car ls) acc)))))
|
||||||
|
(loop ls '()))
|
||||||
|
```
|
||||||
|
|
||||||
|
Use it like so:
|
||||||
|
|
||||||
|
```nohighlight
|
||||||
|
Schemy> (reverse '(1 2 "foo" "bar"))
|
||||||
|
("bar" "foo" 2 1)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Syntax augmentation in Scheme
|
||||||
|
|
||||||
|
For example, we want to augment Schemy with a new syntax for local variable
|
||||||
|
definition, [`let`][schemepl]. Here's what we want to achieve:
|
||||||
|
|
||||||
|
```nohighlight
|
||||||
|
Schemy> (let ((x 1) ; let x = 1
|
||||||
|
(y 2)) ; let y = 2
|
||||||
|
(+ x y)) ; evaluate x + y
|
||||||
|
3
|
||||||
|
```
|
||||||
|
|
||||||
|
The following macro implements the `let` form by using lambda invocation:
|
||||||
|
|
||||||
|
```scheme
|
||||||
|
(define-macro let
|
||||||
|
(lambda args
|
||||||
|
(define specs (car args)) ; ((var1 val1), ...)
|
||||||
|
(define bodies (cdr args)) ; (expr1 ...)
|
||||||
|
(if (null? specs)
|
||||||
|
`((lambda () ,@bodies))
|
||||||
|
(begin
|
||||||
|
(define spec1 (car specs)) ; (var1 val1)
|
||||||
|
(define spec_rest (cdr specs)) ; ((var2 val2) ...)
|
||||||
|
(define inner `((lambda ,(list (car spec1)) ,@bodies) ,(car (cdr spec1))))
|
||||||
|
`(let ,spec_rest ,inner)))))
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
<a id="repl"></a>
|
||||||
|
## Use Interactively (REPL)
|
||||||
|
|
||||||
|
The interpreter can be run interactively, when given a `TextReader` for input
|
||||||
|
and a `TextWriter` for output.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
/// <summary>Starts the Read-Eval-Print loop</summary>
|
||||||
|
/// <param name="input">the input source</param>
|
||||||
|
/// <param name="output">the output target</param>
|
||||||
|
/// <param name="prompt">a string prompt to be printed before each evaluation</param>
|
||||||
|
/// <param name="headers">a head text to be printed at the beginning of the REPL</param>
|
||||||
|
public void REPL(TextReader input, TextWriter output, string prompt = null, string[] headers = null)
|
||||||
|
```
|
||||||
|
|
||||||
|
This can be useful for expose a remote "shell" for the application, or as
|
||||||
|
debugging purposes (see how `src/examples/command_server/` uses the `--repl`
|
||||||
|
command line argument).
|
||||||
|
|
||||||
|
There is an example REPL application in
|
||||||
|
[`src/examples/repl/`](src/examples/repl/) that can be started as a REPL
|
||||||
|
interpreter:
|
||||||
|
|
||||||
|
$ schemy.repl.exe
|
||||||
|
-----------------------------------------------
|
||||||
|
| Schemy - Scheme as a Configuration Language |
|
||||||
|
| Press Ctrl-C to exit |
|
||||||
|
-----------------------------------------------
|
||||||
|
|
||||||
|
Schemy> (define (sum-to n acc)
|
||||||
|
(if (= n 0)
|
||||||
|
acc
|
||||||
|
(sum-to (- n 1) (+ acc n))))
|
||||||
|
|
||||||
|
Schemy> (sum-to 100 0)
|
||||||
|
5050
|
||||||
|
|
||||||
|
Schemy> (sum-to 10000 0) ; proper tail call optimization prevents stack overflow
|
||||||
|
50005000
|
||||||
|
|
||||||
|
Run a script:
|
||||||
|
|
||||||
|
$ schemy.repl.exe <some_file>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Contributing
|
# Contributing
|
||||||
|
|
||||||
|
@ -12,3 +206,10 @@ provided by the bot. You will only need to do this once across all repos using o
|
||||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[schemepl]: http://www.scheme.com/tspl4/start.html#./start:h4
|
||||||
|
[lispy]: http://norvig.com/lispy2.html
|
||||||
|
<!--- vim: set ft=markdown tw=78: -->
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
; --------------------
|
||||||
|
; Define a variable
|
||||||
|
; --------------------
|
||||||
|
(define str "foo bar")
|
||||||
|
str
|
||||||
|
|
||||||
|
; --------------------
|
||||||
|
; Define a function
|
||||||
|
; --------------------
|
||||||
|
(define (square x) (* x x))
|
||||||
|
(square 2) ; call the function
|
||||||
|
|
||||||
|
; --------------------
|
||||||
|
; Create a list of numbers
|
||||||
|
; --------------------
|
||||||
|
(define nums (range 0 10))
|
||||||
|
|
||||||
|
; --------------------
|
||||||
|
; Functional programming:
|
||||||
|
; Map the list into another list using a function
|
||||||
|
; --------------------
|
||||||
|
(map square nums)
|
||||||
|
|
||||||
|
|
||||||
|
; --------------------
|
||||||
|
; Tail call optimization
|
||||||
|
; Reverse a list recursively (without stack overflow)
|
||||||
|
; --------------------
|
||||||
|
(define (reverse ls)
|
||||||
|
(define loop
|
||||||
|
(lambda (ls acc)
|
||||||
|
(if (null? ls) acc
|
||||||
|
(loop (cdr ls) (cons (car ls) acc)))))
|
||||||
|
(loop ls '()))
|
||||||
|
|
||||||
|
(reverse '(1 2 "foo" "bar"))
|
||||||
|
(reverse (range 0 10000)) ; NO STACK OVERFLOW!
|
||||||
|
|
||||||
|
; --------------------
|
||||||
|
; Using LISP macros to extend the language syntax
|
||||||
|
; Here we define a `let` syntax that creates local variable for
|
||||||
|
; only the scope in the `let` block (usage below).
|
||||||
|
; --------------------
|
||||||
|
(define-macro let
|
||||||
|
(lambda args
|
||||||
|
(define specs (car args)) ; ((var1 val1), ...)
|
||||||
|
(define bodies (cdr args)) ; (expr1 ...)
|
||||||
|
(if (null? specs)
|
||||||
|
`((lambda () ,@bodies))
|
||||||
|
(begin
|
||||||
|
(define spec1 (car specs)) ; (var1 val1)
|
||||||
|
(define spec_rest (cdr specs)) ; ((var2 val2) ...)
|
||||||
|
(define inner `((lambda ,(list (car spec1)) ,@bodies) ,(car (cdr spec1))))
|
||||||
|
`(let ,spec_rest ,inner)))))
|
||||||
|
|
||||||
|
; --------------------
|
||||||
|
; Usage of the newly created `let` syntax
|
||||||
|
; --------------------
|
||||||
|
(let ((x 1) ; let x = 1
|
||||||
|
(y 2)) ; let y = 2
|
||||||
|
(+ x y)) ; evaluate x + y
|
||||||
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Schemy;
|
||||||
|
|
||||||
|
namespace Examples.command_server
|
||||||
|
{
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
delegate object Function(object input);
|
||||||
|
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
Interpreter.CreateSymbolTableDelegate extension = _ => new Dictionary<Symbol, object>()
|
||||||
|
{
|
||||||
|
{ Symbol.FromString("get-current-os"), NativeProcedure.Create(() => GetCurrentSystem()) },
|
||||||
|
{ Symbol.FromString("chain"), new NativeProcedure(funcs => new Function(input => funcs.Cast<Function>().Select(b => input = b(input)).Last())) },
|
||||||
|
{ Symbol.FromString("say-hi"), NativeProcedure.Create<Function>(() => name => $"Hello {name}!") },
|
||||||
|
{ Symbol.FromString("man-freebsd"), NativeProcedure.Create<Function>(() => cmd => GetUrl($"https://www.freebsd.org/cgi/man.cgi?query={cmd}&format=ascii")) },
|
||||||
|
{ Symbol.FromString("man-linux"), NativeProcedure.Create<Function>(() => cmd => GetUrl($"http://man7.org/linux/man-pages/man1/{cmd}.1.html")) },
|
||||||
|
{ Symbol.FromString("truncate-string"), NativeProcedure.Create<int, Function>(len => input => ((string)input).Substring(0, len)) },
|
||||||
|
};
|
||||||
|
|
||||||
|
var interpreter = new Interpreter(new[] { extension });
|
||||||
|
|
||||||
|
if (args.Contains("--repl")) // start the REPL with all implemented functions
|
||||||
|
{
|
||||||
|
interpreter.REPL(Console.In, Console.Out);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// starts a TCP server that receives request (cmd <data>) and sends response back.
|
||||||
|
var engines = new Dictionary<string, Function>();
|
||||||
|
foreach (var fn in Directory.GetFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "*.ss"))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Loading file {fn}");
|
||||||
|
LoadScript(interpreter, fn);
|
||||||
|
engines[Path.GetFileNameWithoutExtension(fn)] = (Function)interpreter.Environment[Symbol.FromString("EXECUTE")];
|
||||||
|
}
|
||||||
|
|
||||||
|
string ip = "127.0.0.1"; int port = 8080;
|
||||||
|
var server = new TcpListener(IPAddress.Parse(ip), port);
|
||||||
|
server.Start();
|
||||||
|
Console.WriteLine($"Server started at {ip}:{port}");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var c = server.AcceptTcpClient())
|
||||||
|
using (var cs = c.GetStream())
|
||||||
|
using (var sr = new StreamReader(cs))
|
||||||
|
using (var sw = new StreamWriter(cs))
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Client accepted at {c.Client.RemoteEndPoint}");
|
||||||
|
while (!sr.EndOfStream)
|
||||||
|
{
|
||||||
|
string line = sr.ReadLine();
|
||||||
|
string[] parsed = line.Split(new[] { ' ' }, 2);
|
||||||
|
if (parsed.Length != 2)
|
||||||
|
{
|
||||||
|
sw.WriteLine($"cannot parse {line}");
|
||||||
|
sw.Flush();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string engine = parsed[0], request = parsed[1];
|
||||||
|
if (!engines.ContainsKey(engine))
|
||||||
|
{
|
||||||
|
sw.WriteLine($"engine not found: {engine}");
|
||||||
|
sw.Flush();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string output = (string)(engines[engine](request));
|
||||||
|
sw.WriteLine(output);
|
||||||
|
sw.Flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void LoadScript(Interpreter interpreter, string file)
|
||||||
|
{
|
||||||
|
using (Stream script = File.OpenRead(file))
|
||||||
|
using (TextReader reader = new StreamReader(script))
|
||||||
|
{
|
||||||
|
var res = interpreter.Evaluate(reader);
|
||||||
|
if (res.Error != null) throw res.Error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// support: windows, freebsd, linux, unknown
|
||||||
|
static string GetCurrentSystem()
|
||||||
|
{
|
||||||
|
var os = System.Environment.OSVersion.Platform;
|
||||||
|
if (os.ToString().Contains("Win")) return "windows";
|
||||||
|
if (os == PlatformID.Unix)
|
||||||
|
{
|
||||||
|
Process proc = new Process() { StartInfo = new ProcessStartInfo("uname") { RedirectStandardOutput = true } };
|
||||||
|
proc.Start();
|
||||||
|
var output = proc.StandardOutput.ReadToEnd().Trim();
|
||||||
|
proc.WaitForExit();
|
||||||
|
|
||||||
|
foreach (var w in new[] { "freebsd", "linux" })
|
||||||
|
{
|
||||||
|
if (output.IndexOf(w, StringComparison.OrdinalIgnoreCase) >= 0) return w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
static string GetUrl(string url)
|
||||||
|
{
|
||||||
|
using (var wc = new System.Net.WebClient())
|
||||||
|
{
|
||||||
|
return wc.DownloadString(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
# EXAMPLE: A CONFIGURABLE COMMAND SERVER
|
||||||
|
|
||||||
|
This application is an example use of Schemy to load configurable command
|
||||||
|
processing pipelines and serve the loaded commands via TCP channel.
|
||||||
|
|
||||||
|
In this application, the server does the following things:
|
||||||
|
|
||||||
|
1. It extends an embedded Schemy interpreter with some functions implemented
|
||||||
|
in C#.
|
||||||
|
|
||||||
|
2. It finds `.ss` scripts which defines a command processing pipeline by using
|
||||||
|
those implemented functions.
|
||||||
|
|
||||||
|
3. The server finds and persists the composes pipeline from a script by
|
||||||
|
looking for the symbol `EXECUTE` which should be of type `Func<object,
|
||||||
|
object>`.
|
||||||
|
|
||||||
|
4. When a command request comes in, it simply invokes the corresponding
|
||||||
|
command processor (the one defined by `EXECUTE`), and responses with the
|
||||||
|
result.
|
||||||
|
|
||||||
|
A simple example is the [`say-hi.ss`](say-hi.ss) script:
|
||||||
|
|
||||||
|
```scheme
|
||||||
|
; This command processor would echo an input string `name` in the format:
|
||||||
|
;
|
||||||
|
; hello name!
|
||||||
|
|
||||||
|
(define EXECUTE (say-hi))
|
||||||
|
```
|
||||||
|
|
||||||
|
As a complex example, [`man.ss`](man.ss) defines a online man-page lookup:
|
||||||
|
|
||||||
|
```scheme
|
||||||
|
(define EXECUTE
|
||||||
|
(let ((os (get-current-os))
|
||||||
|
(max-length 500))
|
||||||
|
(chain ; chain functions together
|
||||||
|
(cond ; pick a manpage lookup based on OS
|
||||||
|
((equal? os "freebsd") (man-freebsd))
|
||||||
|
((equal? os "linux") (man-linux))
|
||||||
|
(else (man-freebsd)))
|
||||||
|
(truncate-string max-length)))) ; truncate output string to a max length
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
With these two scripts loaded the command server, a TCP client can issue commands
|
||||||
|
`man <unix_command>` and `sai-hi <name>` to the server:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ncat 127.0.0.1 8080
|
||||||
|
|
||||||
|
|
||||||
|
say-hi John Doe
|
||||||
|
Hello John Doe!
|
||||||
|
|
||||||
|
|
||||||
|
man ls
|
||||||
|
|
||||||
|
LS(1) FreeBSD General Commands Manual LS(1)
|
||||||
|
|
||||||
|
NAME
|
||||||
|
ls -- list directory contents
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
ls [--libxo] [-ABCFGHILPRSTUWZabcdfghiklmnopqrstuwxy1,] [-D format]
|
||||||
|
[file ...]
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
For each operand that names a file of a type other than directory, ls
|
||||||
|
displays its name as well as any requested, associated information. For
|
||||||
|
each operand that names a file of type directory, ls displays the names
|
||||||
|
of files contained within that directory, as well as any requested,
|
||||||
|
```
|
|
@ -0,0 +1,65 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{88D7E5A3-0BA9-4155-B151-839FF5734F7C}</ProjectGuid>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
|
<RootNamespace>command_server</RootNamespace>
|
||||||
|
<AssemblyName>command_server</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
|
<TargetFrameworkProfile />
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
|
<Reference Include="Microsoft.CSharp" />
|
||||||
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Net.Http" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Program.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\schemy\schemy.csproj">
|
||||||
|
<Project>{e54139b7-cb81-4883-b8cd-40bab5420eb8}</Project>
|
||||||
|
<Name>schemy</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Properties\" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
<Target Name="BeforeBuild">
|
||||||
|
</Target>
|
||||||
|
<Target Name="AfterBuild">
|
||||||
|
<Copy SourceFiles="man.ss" DestinationFiles="$(TargetDir)man.ss" />
|
||||||
|
<Copy SourceFiles="say-hi.ss" DestinationFiles="$(TargetDir)say-hi.ss" />
|
||||||
|
</Target>
|
||||||
|
</Project>
|
|
@ -0,0 +1,41 @@
|
||||||
|
; This script will be load by the server as command `man`. The command
|
||||||
|
; is consistent of the following functions chained together:
|
||||||
|
;
|
||||||
|
; 1. An online man-page look up - it detects the current operating system and
|
||||||
|
; decides to use either a linux or freebsd man page web API for the look up.
|
||||||
|
;
|
||||||
|
; 2. A string truncator `truncate-string` - it truncates the input string, in
|
||||||
|
; this case the output of the man-page lookup, to the specified number of
|
||||||
|
; characters.
|
||||||
|
;
|
||||||
|
; The client of the command server connects via raw RCP protocol, and can issue
|
||||||
|
; commands like:
|
||||||
|
;
|
||||||
|
; man ls
|
||||||
|
;
|
||||||
|
; and gets response like:
|
||||||
|
;
|
||||||
|
; LS(1) FreeBSD General Commands Manual LS(1)
|
||||||
|
;
|
||||||
|
; NAME
|
||||||
|
; ls -- list directory contents
|
||||||
|
;
|
||||||
|
; SYNOPSIS
|
||||||
|
; ls [--libxo] [-ABCFGHILPRSTUWZabcdfghiklmnopqrstuwxy1,] [-D format]
|
||||||
|
; [file ...]
|
||||||
|
;
|
||||||
|
; DESCRIPTION
|
||||||
|
; For each operand that names a file of a type other than directory, ls
|
||||||
|
; displays its name as well as any requested, associated information. For
|
||||||
|
; each operand that names a file of type directory, ls displays the names
|
||||||
|
; of files contained within that directory, as well as any requested,
|
||||||
|
|
||||||
|
(define EXECUTE
|
||||||
|
(let ((os (get-current-os))
|
||||||
|
(max-length 500))
|
||||||
|
(chain ; chain functions together
|
||||||
|
(cond ; pick a manpage lookup based on OS
|
||||||
|
((equal? os "freebsd") (man-freebsd))
|
||||||
|
((equal? os "linux") (man-linux))
|
||||||
|
(else (man-freebsd)))
|
||||||
|
(truncate-string max-length)))) ; truncate output string to a max length
|
|
@ -0,0 +1,5 @@
|
||||||
|
; This command processor would echo an input string `name` in the format:
|
||||||
|
;
|
||||||
|
; hello name!
|
||||||
|
|
||||||
|
(define EXECUTE (say-hi))
|
|
@ -0,0 +1,2 @@
|
||||||
|
; `.init.ss` is picked up by interpreter automatically
|
||||||
|
(define square (lambda (x) (* x x)))
|
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
namespace Schemy
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length > 0 && File.Exists(args[0]))
|
||||||
|
{
|
||||||
|
// evaluate input file's content
|
||||||
|
var file = args[0];
|
||||||
|
var interpreter = new Interpreter();
|
||||||
|
|
||||||
|
using (TextReader reader = new StreamReader(file))
|
||||||
|
{
|
||||||
|
object res = interpreter.Evaluate(reader);
|
||||||
|
Console.WriteLine(Utils.PrintExpr(res));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// starts the REPL
|
||||||
|
var interpreter = new Interpreter();
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
"-----------------------------------------------",
|
||||||
|
"| Schemy - Scheme as a Configuration Language |",
|
||||||
|
"| Press Ctrl-C to exit |",
|
||||||
|
"-----------------------------------------------",
|
||||||
|
};
|
||||||
|
|
||||||
|
interpreter.REPL(Console.In, Console.Out, "Schemy> ", headers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{56DADEFC-6B30-4C0F-AAEA-23D684BD817F}</ProjectGuid>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
|
<RootNamespace>Schemy</RootNamespace>
|
||||||
|
<AssemblyName>schemy.repl</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
|
<TargetFrameworkProfile />
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
|
<Reference Include="Microsoft.CSharp" />
|
||||||
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Net.Http" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Program.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\schemy\schemy.csproj">
|
||||||
|
<Project>{e54139b7-cb81-4883-b8cd-40bab5420eb8}</Project>
|
||||||
|
<Name>schemy</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Properties\" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
<Target Name="BeforeBuild">
|
||||||
|
</Target>
|
||||||
|
-->
|
||||||
|
<Target Name="AfterBuild">
|
||||||
|
<Copy SourceFiles=".init.ss" DestinationFiles="$(TargetDir).init.ss" />
|
||||||
|
</Target>
|
||||||
|
</Project>
|
|
@ -0,0 +1,2 @@
|
||||||
|
; `.init.ss` is picked up by interpreter automatically
|
||||||
|
(define square (lambda (x) (* x x)))
|
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
namespace Schemy
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length > 0 && File.Exists(args[0]))
|
||||||
|
{
|
||||||
|
// evaluate input file's content
|
||||||
|
var file = args[0];
|
||||||
|
var interpreter = new Interpreter();
|
||||||
|
|
||||||
|
using (TextReader reader = new StreamReader(file))
|
||||||
|
{
|
||||||
|
object res = interpreter.Evaluate(reader);
|
||||||
|
Console.WriteLine(Utils.PrintExpr(res));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// starts the REPL
|
||||||
|
var interpreter = new Interpreter();
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
"-----------------------------------------------",
|
||||||
|
"| Schemy - Scheme as a Configuration Language |",
|
||||||
|
"| Press Ctrl-C to exit |",
|
||||||
|
"-----------------------------------------------",
|
||||||
|
};
|
||||||
|
|
||||||
|
interpreter.REPL(Console.In, Console.Out, "Schemy> ", headers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{56DADEFC-6B30-4C0F-AAEA-23D684BD817F}</ProjectGuid>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
|
<RootNamespace>Schemy</RootNamespace>
|
||||||
|
<AssemblyName>schemy.repl</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
|
<TargetFrameworkProfile />
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
|
<Reference Include="Microsoft.CSharp" />
|
||||||
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Net.Http" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Program.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\schemy\schemy.csproj">
|
||||||
|
<Project>{e54139b7-cb81-4883-b8cd-40bab5420eb8}</Project>
|
||||||
|
<Name>schemy</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Properties\" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
<Target Name="BeforeBuild">
|
||||||
|
</Target>
|
||||||
|
-->
|
||||||
|
<Target Name="AfterBuild">
|
||||||
|
<Copy SourceFiles=".init.ss" DestinationFiles="$(TargetDir).init.ss" />
|
||||||
|
</Target>
|
||||||
|
</Project>
|
|
@ -0,0 +1,40 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 14
|
||||||
|
VisualStudioVersion = 14.0.25420.1
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "schemy", "schemy\schemy.csproj", "{E54139B7-CB81-4883-B8CD-40BAB5420EB8}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "test", "test\test.csproj", "{4A62A84F-58AA-4D1C-AA7C-D3CDF0C3FFA6}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "example.repl", "examples\repl\example.repl.csproj", "{56DADEFC-6B30-4C0F-AAEA-23D684BD817F}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "example.command_server", "examples\command_server\example.command_server.csproj", "{88D7E5A3-0BA9-4155-B151-839FF5734F7C}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{E54139B7-CB81-4883-B8CD-40BAB5420EB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{E54139B7-CB81-4883-B8CD-40BAB5420EB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{E54139B7-CB81-4883-B8CD-40BAB5420EB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{E54139B7-CB81-4883-B8CD-40BAB5420EB8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{4A62A84F-58AA-4D1C-AA7C-D3CDF0C3FFA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4A62A84F-58AA-4D1C-AA7C-D3CDF0C3FFA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4A62A84F-58AA-4D1C-AA7C-D3CDF0C3FFA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4A62A84F-58AA-4D1C-AA7C-D3CDF0C3FFA6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{56DADEFC-6B30-4C0F-AAEA-23D684BD817F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{56DADEFC-6B30-4C0F-AAEA-23D684BD817F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{56DADEFC-6B30-4C0F-AAEA-23D684BD817F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{56DADEFC-6B30-4C0F-AAEA-23D684BD817F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{88D7E5A3-0BA9-4155-B151-839FF5734F7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{88D7E5A3-0BA9-4155-B151-839FF5734F7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{88D7E5A3-0BA9-4155-B151-839FF5734F7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{88D7E5A3-0BA9-4155-B151-839FF5734F7C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
|
@ -0,0 +1,165 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
namespace Schemy
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extend the interpreter with essential builtin functionalities
|
||||||
|
/// </summary>
|
||||||
|
public class Builtins
|
||||||
|
{
|
||||||
|
public static IDictionary<Symbol, object> CreateBuiltins(Interpreter interpreter)
|
||||||
|
{
|
||||||
|
var builtins = new Dictionary<Symbol, object>();
|
||||||
|
|
||||||
|
builtins[Symbol.FromString("+")] = new NativeProcedure(Utils.MakeVariadic(Add), "+");
|
||||||
|
builtins[Symbol.FromString("-")] = new NativeProcedure(Utils.MakeVariadic(Minus), "-");
|
||||||
|
builtins[Symbol.FromString("*")] = new NativeProcedure(Utils.MakeVariadic(Multiply), "*");
|
||||||
|
builtins[Symbol.FromString("/")] = new NativeProcedure(Utils.MakeVariadic(Divide), "/");
|
||||||
|
builtins[Symbol.FromString("=")] = NativeProcedure.Create<double, double, bool>((x, y) => x == y, "=");
|
||||||
|
builtins[Symbol.FromString("<")] = NativeProcedure.Create<double, double, bool>((x, y) => x < y, "<");
|
||||||
|
builtins[Symbol.FromString("<=")] = NativeProcedure.Create<double, double, bool>((x, y) => x <= y, "<=");
|
||||||
|
builtins[Symbol.FromString(">")] = NativeProcedure.Create<double, double, bool>((x, y) => x > y, ">");
|
||||||
|
builtins[Symbol.FromString(">=")] = NativeProcedure.Create<double, double, bool>((x, y) => x >= y, ">=");
|
||||||
|
builtins[Symbol.FromString("eq?")] = NativeProcedure.Create<object, object, bool>((x, y) => object.ReferenceEquals(x, y), "eq?");
|
||||||
|
builtins[Symbol.FromString("equal?")] = NativeProcedure.Create<object, object, bool>(EqualImpl, "equal?");
|
||||||
|
builtins[Symbol.FromString("boolean?")] = NativeProcedure.Create<object, bool>(x => x is bool, "boolean?");
|
||||||
|
builtins[Symbol.FromString("num?")] = NativeProcedure.Create<object, bool>(x => x is int || x is double, "num?");
|
||||||
|
builtins[Symbol.FromString("string?")] = NativeProcedure.Create<object, bool>(x => x is string, "string?");
|
||||||
|
builtins[Symbol.FromString("symbol?")] = NativeProcedure.Create<object, bool>(x => x is Symbol, "symbol?");
|
||||||
|
builtins[Symbol.FromString("list?")] = NativeProcedure.Create<object, bool>(x => x is List<object>, "list?");
|
||||||
|
builtins[Symbol.FromString("map")] = NativeProcedure.Create<ICallable, List<object>, List<object>>((func, ls) => ls.Select(x => func.Call(new List<object> { x })).ToList());
|
||||||
|
builtins[Symbol.FromString("reverse")] = NativeProcedure.Create<List<object>, List<object>>(ls => ls.Reverse<object>().ToList());
|
||||||
|
builtins[Symbol.FromString("range")] = new NativeProcedure(RangeImpl, "range");
|
||||||
|
builtins[Symbol.FromString("apply")] = NativeProcedure.Create<ICallable, List<object>, object>((proc, args) => proc.Call(args), "apply");
|
||||||
|
builtins[Symbol.FromString("list")] = new NativeProcedure(args => args, "list");
|
||||||
|
builtins[Symbol.FromString("list-ref")] = NativeProcedure.Create<List<object>, int, object>((ls, idx) => ls[idx]);
|
||||||
|
builtins[Symbol.FromString("length")] = NativeProcedure.Create<List<object>, int>(list => list.Count, "length");
|
||||||
|
builtins[Symbol.FromString("car")] = NativeProcedure.Create<List<object>, object>(args => args[0], "car");
|
||||||
|
builtins[Symbol.FromString("cdr")] = NativeProcedure.Create<List<object>, List<object>>(args => args.Skip(1).ToList(), "cdr");
|
||||||
|
builtins[Symbol.CONS] = NativeProcedure.Create<object, List<object>, List<object>>((x, ys) => Enumerable.Concat(new[] { x }, ys).ToList(), "cons");
|
||||||
|
builtins[Symbol.FromString("not")] = NativeProcedure.Create<bool, bool>(x => !x, "not");
|
||||||
|
builtins[Symbol.APPEND] = NativeProcedure.Create<List<object>, List<object>, List<object>>((l1, l2) => Enumerable.Concat(l1, l2).ToList(), "append");
|
||||||
|
builtins[Symbol.FromString("null?")] = NativeProcedure.Create<object, bool>(x => x is List<object> && ((List<object>)x).Count == 0, "null?");
|
||||||
|
builtins[Symbol.FromString("assert")] = new NativeProcedure(AssertImpl, "assert");
|
||||||
|
builtins[Symbol.FromString("load")] = NativeProcedure.Create<string, None>(filename => LoadImpl(interpreter, filename), "load");
|
||||||
|
builtins[Symbol.FromString("null")] = NativeProcedure.Create<object>(() => (object)null, "null");
|
||||||
|
builtins[Symbol.FromString("null?")] = NativeProcedure.Create<object, bool>(x => x is List<object> && ((List<object>)x).Count == 0, "null?");
|
||||||
|
builtins[Symbol.FromString("assert")] = new NativeProcedure(AssertImpl, "assert");
|
||||||
|
builtins[Symbol.FromString("load")] = NativeProcedure.Create<string, None>(filename => LoadImpl(interpreter, filename), "load");
|
||||||
|
|
||||||
|
return builtins;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Builtin Implementations
|
||||||
|
|
||||||
|
private static List<object> RangeImpl(List<object> args)
|
||||||
|
{
|
||||||
|
Utils.CheckSyntax(args, args.Count >= 1 && args.Count <= 3);
|
||||||
|
foreach (var item in args)
|
||||||
|
{
|
||||||
|
Utils.CheckSyntax(args, item is int, "items must be integers");
|
||||||
|
}
|
||||||
|
|
||||||
|
int start, end, step;
|
||||||
|
if (args.Count == 1)
|
||||||
|
{
|
||||||
|
start = 0;
|
||||||
|
end = (int)args[0];
|
||||||
|
step = 1;
|
||||||
|
}
|
||||||
|
else if (args.Count == 2)
|
||||||
|
{
|
||||||
|
start = (int)args[0];
|
||||||
|
end = (int)args[1];
|
||||||
|
step = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
start = (int)args[0];
|
||||||
|
end = (int)args[1];
|
||||||
|
step = (int)args[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start < end) Utils.CheckSyntax(args, step > 0, "step must make the sequence end");
|
||||||
|
if (start > end) Utils.CheckSyntax(args, step < 0, "step must make the sequence end");
|
||||||
|
|
||||||
|
var res = new List<object>();
|
||||||
|
|
||||||
|
if (start <= end) for (int i = start; i < end; i += step) res.Add(i);
|
||||||
|
else for (int i = start; i > end; i += step) res.Add(i);
|
||||||
|
|
||||||
|
res.TrimExcess();
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static None AssertImpl(List<object> args)
|
||||||
|
{
|
||||||
|
Utils.CheckArity(args, 1, 2);
|
||||||
|
string msg = "Assertion failed";
|
||||||
|
msg += args.Count > 1 ? ": " + Utils.ConvertType<string>(args[1]) : string.Empty;
|
||||||
|
bool pred = Utils.ConvertType<bool>(args[0]);
|
||||||
|
if (!pred) throw new AssertionFailedError(msg);
|
||||||
|
return None.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static None LoadImpl(Interpreter interpreter, string filename)
|
||||||
|
{
|
||||||
|
using (TextReader reader = new StreamReader(interpreter.FileSystemAccessor.OpenRead(filename)))
|
||||||
|
{
|
||||||
|
interpreter.Evaluate(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
return None.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool EqualImpl(object x, object y)
|
||||||
|
{
|
||||||
|
if (object.Equals(x, y)) return true;
|
||||||
|
if (x == null || y == null) return false;
|
||||||
|
|
||||||
|
if (x is IList<object> && y is IList<object>)
|
||||||
|
{
|
||||||
|
var x2 = (IList<object>)x;
|
||||||
|
var y2 = (IList<object>)y;
|
||||||
|
if (x2.Count != y2.Count) return false;
|
||||||
|
return Enumerable.Zip(x2, y2, (a, b) => Tuple.Create(a, b))
|
||||||
|
.All(pair => EqualImpl(pair.Item1, pair.Item2));
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object Add(object x, object y)
|
||||||
|
{
|
||||||
|
if (x is int && y is int) return (int)x + (int)y;
|
||||||
|
return (double)System.Convert.ChangeType(x, typeof(double)) + (double)System.Convert.ChangeType(y, typeof(double));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object Minus(object x, object y)
|
||||||
|
{
|
||||||
|
if (x is int && y is int) return (int)x - (int)y;
|
||||||
|
return (double)System.Convert.ChangeType(x, typeof(double)) - (double)System.Convert.ChangeType(y, typeof(double));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object Multiply(object x, object y)
|
||||||
|
{
|
||||||
|
if (x is int && y is int) return (int)x * (int)y;
|
||||||
|
return (double)System.Convert.ChangeType(x, typeof(double)) * (double)System.Convert.ChangeType(y, typeof(double));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object Divide(object x, object y)
|
||||||
|
{
|
||||||
|
if (x is int && y is int) return (int)x / (int)y;
|
||||||
|
return (double)System.Convert.ChangeType(x, typeof(double)) / (double)System.Convert.ChangeType(y, typeof(double));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion Builtin Implementations
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
namespace Schemy
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public class None
|
||||||
|
{
|
||||||
|
public static readonly None Instance = new None();
|
||||||
|
}
|
||||||
|
|
||||||
|
class AssertionFailedError : Exception
|
||||||
|
{
|
||||||
|
public AssertionFailedError(string msg) : base(msg)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SyntaxError : Exception
|
||||||
|
{
|
||||||
|
public SyntaxError(string msg) : base(msg)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Poor man's discreminated union
|
||||||
|
/// </summary>
|
||||||
|
public class Union<T1, T2>
|
||||||
|
{
|
||||||
|
private readonly object data;
|
||||||
|
public Union(T1 data)
|
||||||
|
{
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Union(T2 data)
|
||||||
|
{
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TResult Use<TResult>(Func<T1, TResult> func1, Func<T2, TResult> func2)
|
||||||
|
{
|
||||||
|
if (this.data is T1)
|
||||||
|
{
|
||||||
|
return func1((T1)this.data);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return func2((T2)this.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
namespace Schemy
|
||||||
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tracks the state of an interpreter or a procedure. It supports lexical scoping.
|
||||||
|
/// </summary>
|
||||||
|
public class Environment
|
||||||
|
{
|
||||||
|
private readonly IDictionary<Symbol, object> store;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The enclosing environment. For top level env, this is null.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Environment outer;
|
||||||
|
|
||||||
|
public Environment(IDictionary<Symbol, object> env, Environment outer)
|
||||||
|
{
|
||||||
|
this.store = env;
|
||||||
|
this.outer = outer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Environment CreateEmpty()
|
||||||
|
{
|
||||||
|
return new Environment(new Dictionary<Symbol, object>(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Environment FromVariablesAndValues(Union<Symbol, List<Symbol>> parameters, List<object> values, Environment outer)
|
||||||
|
{
|
||||||
|
return parameters.Use(
|
||||||
|
@params => new Environment(new Dictionary<Symbol, object>() { { @params, values } }, outer),
|
||||||
|
@params =>
|
||||||
|
{
|
||||||
|
if (values.Count != @params.Count)
|
||||||
|
{
|
||||||
|
throw new SyntaxError(string.Format("Unexpected number of arguments. Expecting {0}, Got {1}.", @params.Count, values.Count));
|
||||||
|
}
|
||||||
|
|
||||||
|
var dict = new Dictionary<Symbol, object>();
|
||||||
|
for (int i = 0; i < values.Count; i++)
|
||||||
|
{
|
||||||
|
dict[@params[i]] = values[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Environment(dict, outer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to get the value of the symbol. If it's not found in current env, recursively try the enclosing env.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="val">The value of the symbol to find</param>
|
||||||
|
/// <returns>if the symbol's value could be found</returns>
|
||||||
|
public bool TryGetValue(Symbol sym, out object val)
|
||||||
|
{
|
||||||
|
Environment env = this.TryFindContainingEnv(sym);
|
||||||
|
if (env != null)
|
||||||
|
{
|
||||||
|
val = env.store[sym];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
val = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to find the env that actually defines the symbol
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sym">The symbol to find</param>
|
||||||
|
/// <returns>the env that defines the symbol</returns>
|
||||||
|
public Environment TryFindContainingEnv(Symbol sym)
|
||||||
|
{
|
||||||
|
object val;
|
||||||
|
if (this.store.TryGetValue(sym, out val)) return this;
|
||||||
|
if (this.outer != null) return this.outer.TryFindContainingEnv(sym);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object this[Symbol sym]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
object val;
|
||||||
|
if (this.TryGetValue(sym, out val))
|
||||||
|
{
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new KeyNotFoundException(string.Format("Symbol not defined: {0}", sym));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
this.store[sym] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
namespace Schemy
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The only interface that the file system should be exposed within a Schemy interpreter.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// One could implement this interface in a way such that the interpreter can be used to access "files" in
|
||||||
|
/// any logical virtual file system. For security purposes, one could also choose to not implement, say,
|
||||||
|
/// <see cref="IFileSystemAccessor.OpenWrite"/> if the interpreter is used in a way that write does not need to supported.
|
||||||
|
/// The other (higher) level of protection would be to not expose any builtin function for writing to the file system.
|
||||||
|
/// </remarks>
|
||||||
|
public interface IFileSystemAccessor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Opens the path for read
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path</param>
|
||||||
|
/// <returns>the stream to read</returns>
|
||||||
|
Stream OpenRead(string path);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opens the path for write
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path</param>
|
||||||
|
/// <returns>the stream to write</returns>
|
||||||
|
Stream OpenWrite(string path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An implementation of <see cref="IFileSystemAccessor"/> that grants readonly access to the host file system.
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="Schemy.IFileSystemAccessor" />
|
||||||
|
public class ReadOnlyFileSystemAccessor : IFileSystemAccessor
|
||||||
|
{
|
||||||
|
public Stream OpenRead(string path)
|
||||||
|
{
|
||||||
|
return File.OpenRead(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream OpenWrite(string path)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException("Writing to file system is not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,217 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Schemy
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a procedure value in Scheme
|
||||||
|
/// </summary>
|
||||||
|
interface ICallable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes this procedure
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">The arguments. These are the `cdr` of the s-expression for the procedure invocation.</param>
|
||||||
|
/// <returns>the result of the procedure invocation</returns>
|
||||||
|
object Call(List<object> args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A procedure implemented in Scheme
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="Schemy.ICallable" />
|
||||||
|
public class Procedure : ICallable
|
||||||
|
{
|
||||||
|
private readonly Union<Symbol, List<Symbol>> parameters;
|
||||||
|
private readonly object body;
|
||||||
|
private readonly Environment env;
|
||||||
|
|
||||||
|
public Procedure(Union<Symbol, List<Symbol>> parameters, object body, Environment env)
|
||||||
|
{
|
||||||
|
this.parameters = parameters;
|
||||||
|
this.body = body;
|
||||||
|
this.env = env;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Body
|
||||||
|
{
|
||||||
|
get { return this.body; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Union<Symbol, List<Symbol>> Parameters
|
||||||
|
{
|
||||||
|
get { return this.parameters; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public Environment Env
|
||||||
|
{
|
||||||
|
get { return this.env; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes this procedure
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Implementation note: under normal function invocation scenarios, this method is not used. Instead,
|
||||||
|
/// a tail call optimization is used in the interpreter evaluation phase that runs Scheme functions.
|
||||||
|
///
|
||||||
|
/// This method is useful however, in macro expansions, and any other occasions where the tail call optimization
|
||||||
|
/// is not (yet) implemented.
|
||||||
|
///
|
||||||
|
/// <see cref="Interpreter.EvaluateExpression(object, Environment)"/>
|
||||||
|
/// </remarks>
|
||||||
|
public object Call(List<object> args)
|
||||||
|
{
|
||||||
|
// NOTE: This is not needed for regular function invoke after the tail call optimization.
|
||||||
|
// a (non-native) procedure is now optimized into evaluating the body under the environment
|
||||||
|
// formed by the (params, args). So the `Call` method will never be used.
|
||||||
|
return Interpreter.EvaluateExpression(this.body, Environment.FromVariablesAndValues(this.parameters, args, this.env));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Prints the implementation of the function.
|
||||||
|
/// </summary>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
var form = new List<object> { Symbol.LAMBDA, this.parameters.Use(sym => (object)sym, syms => syms.Cast<object>().ToList()), this.body };
|
||||||
|
return Utils.PrintExpr(form);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A procedure implemented in .NET
|
||||||
|
/// </summary>
|
||||||
|
/// <seealso cref="Schemy.ICallable" />
|
||||||
|
public class NativeProcedure : ICallable
|
||||||
|
{
|
||||||
|
private readonly Func<List<object>, object> func;
|
||||||
|
private readonly string name;
|
||||||
|
|
||||||
|
public NativeProcedure(Func<List<object>, object> func, string name = null)
|
||||||
|
{
|
||||||
|
this.func = func;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Call(List<object> args)
|
||||||
|
{
|
||||||
|
return this.func(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convenient function method to create a native procedure and doing arity and type check for inputs. It makes the input function
|
||||||
|
/// implementation strongly typed.
|
||||||
|
/// </summary>
|
||||||
|
/// <see cref="Create{T1, T2}(Func{T1, T2}, string)"/>
|
||||||
|
public static NativeProcedure Create<T1, T2, T3, T4, T5, T6, T7, T8>(Func<T1, T2, T3, T4, T5, T6, T7, T8> func, string name = null)
|
||||||
|
{
|
||||||
|
return new NativeProcedure(args =>
|
||||||
|
{
|
||||||
|
Utils.CheckArity(args, 7);
|
||||||
|
return func(
|
||||||
|
Utils.ConvertType<T1>(args[0]),
|
||||||
|
Utils.ConvertType<T2>(args[1]),
|
||||||
|
Utils.ConvertType<T3>(args[2]),
|
||||||
|
Utils.ConvertType<T4>(args[3]),
|
||||||
|
Utils.ConvertType<T5>(args[4]),
|
||||||
|
Utils.ConvertType<T6>(args[5]),
|
||||||
|
Utils.ConvertType<T7>(args[6])
|
||||||
|
);
|
||||||
|
}, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convenient function method to create a native procedure and doing arity and type check for inputs. It makes the input function
|
||||||
|
/// implementation strongly typed.
|
||||||
|
/// </summary>
|
||||||
|
/// <see cref="Create{T1, T2}(Func{T1, T2}, string)"/>
|
||||||
|
public static NativeProcedure Create<T1, T2, T3, T4, T5>(Func<T1, T2, T3, T4, T5> func, string name = null)
|
||||||
|
{
|
||||||
|
return new NativeProcedure(args =>
|
||||||
|
{
|
||||||
|
Utils.CheckArity(args, 4);
|
||||||
|
return func(
|
||||||
|
Utils.ConvertType<T1>(args[0]),
|
||||||
|
Utils.ConvertType<T2>(args[1]),
|
||||||
|
Utils.ConvertType<T3>(args[2]),
|
||||||
|
Utils.ConvertType<T4>(args[3]));
|
||||||
|
}, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convenient function method to create a native procedure and doing arity and type check for inputs. It makes the input function
|
||||||
|
/// implementation strongly typed.
|
||||||
|
/// </summary>
|
||||||
|
/// <see cref="Create{T1, T2}(Func{T1, T2}, string)"/>
|
||||||
|
public static NativeProcedure Create<T1, T2, T3, T4>(Func<T1, T2, T3, T4> func, string name = null)
|
||||||
|
{
|
||||||
|
return new NativeProcedure(args =>
|
||||||
|
{
|
||||||
|
Utils.CheckArity(args, 3);
|
||||||
|
return func(
|
||||||
|
Utils.ConvertType<T1>(args[0]),
|
||||||
|
Utils.ConvertType<T2>(args[1]),
|
||||||
|
Utils.ConvertType<T3>(args[2]));
|
||||||
|
}, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convenient function method to create a native procedure and doing arity and type check for inputs. It makes the input function
|
||||||
|
/// implementation strongly typed.
|
||||||
|
/// </summary>
|
||||||
|
/// <see cref="Create{T1, T2}(Func{T1, T2}, string)"/>
|
||||||
|
public static NativeProcedure Create<T1, T2, T3>(Func<T1, T2, T3> func, string name = null)
|
||||||
|
{
|
||||||
|
return new NativeProcedure(args =>
|
||||||
|
{
|
||||||
|
Utils.CheckArity(args, 2);
|
||||||
|
return func(Utils.ConvertType<T1>(args[0]), Utils.ConvertType<T2>(args[1]));
|
||||||
|
}, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convenient function method to create a native procedure and doing arity and type check for inputs. It makes the input function
|
||||||
|
/// implementation strongly typed.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T1">The type of the 1st argument</typeparam>
|
||||||
|
/// <typeparam name="T2">The type of the 2nd argument</typeparam>
|
||||||
|
/// <param name="func">The function implementation</param>
|
||||||
|
/// <param name="name">The name of the function</param>
|
||||||
|
public static NativeProcedure Create<T1, T2>(Func<T1, T2> func, string name = null)
|
||||||
|
{
|
||||||
|
return new NativeProcedure(args =>
|
||||||
|
{
|
||||||
|
Utils.CheckArity(args, 1);
|
||||||
|
return func(Utils.ConvertType<T1>(args[0]));
|
||||||
|
}, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convenient function method to create a native procedure and doing arity and type check for inputs. It makes the input function
|
||||||
|
/// implementation strongly typed.
|
||||||
|
/// </summary>
|
||||||
|
/// <see cref="Create{T1, T2}(Func{T1, T2}, string)"/>
|
||||||
|
public static NativeProcedure Create<T1>(Func<T1> func, string name = null)
|
||||||
|
{
|
||||||
|
return new NativeProcedure(args =>
|
||||||
|
{
|
||||||
|
Utils.CheckArity(args, 0);
|
||||||
|
return func();
|
||||||
|
}, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ToString implementation
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>the string representation</returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format("#<NativeProcedure:{0}>", string.IsNullOrEmpty(this.name) ? "noname" : this.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Schemy
|
||||||
|
{
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes the interpreter with a init script if present.
|
||||||
|
/// </summary>
|
||||||
|
static void Initialize(Interpreter interpreter)
|
||||||
|
{
|
||||||
|
string initFile = Path.Combine(Path.GetDirectoryName(typeof(Interpreter).Assembly.Location), ".init.ss");
|
||||||
|
if (File.Exists(initFile))
|
||||||
|
{
|
||||||
|
using (var reader = new StreamReader(initFile))
|
||||||
|
{
|
||||||
|
var res = interpreter.Evaluate(reader);
|
||||||
|
if (res.Error != null)
|
||||||
|
{
|
||||||
|
Console.WriteLine(string.Format("Error loading {0}: {1}{2}",
|
||||||
|
initFile,
|
||||||
|
System.Environment.NewLine,
|
||||||
|
res.Error));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("Loaded init file: " + initFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length > 0 && File.Exists(args[0]))
|
||||||
|
{
|
||||||
|
// evaluate input file's content
|
||||||
|
var file = args[0];
|
||||||
|
var interpreter = new Interpreter();
|
||||||
|
Initialize(interpreter);
|
||||||
|
|
||||||
|
using (TextReader reader = new StreamReader(file))
|
||||||
|
{
|
||||||
|
object res = interpreter.Evaluate(reader);
|
||||||
|
Console.WriteLine(Utils.PrintExpr(res));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// starts the REPL
|
||||||
|
var interpreter = new Interpreter();
|
||||||
|
Initialize(interpreter);
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
"-----------------------------------------------",
|
||||||
|
"| Schemy - Scheme as a Configuration Language |",
|
||||||
|
"| Press Ctrl-C to exit |",
|
||||||
|
"-----------------------------------------------",
|
||||||
|
};
|
||||||
|
|
||||||
|
interpreter.REPL(Console.In, Console.Out, "Schemy> ", headers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyTitle("schemy")]
|
||||||
|
[assembly: AssemblyDescription("A lightweight, embeddable Scheme-like language interpreter")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("Microsoft")]
|
||||||
|
[assembly: AssemblyProduct("schemy")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © 2017")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("e54139b7-cb81-4883-b8cd-40bab5420eb8")]
|
||||||
|
|
||||||
|
// Version information for an assembly consists of the following four values:
|
||||||
|
//
|
||||||
|
// Major Version
|
||||||
|
// Minor Version
|
||||||
|
// Build Number
|
||||||
|
// Revision
|
||||||
|
//
|
||||||
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
|
// by using the '*' as shown below:
|
||||||
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
|
[assembly: AssemblyVersion("1.0.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
@ -0,0 +1,583 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
namespace Schemy
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
public class Interpreter
|
||||||
|
{
|
||||||
|
private readonly Environment environment;
|
||||||
|
private readonly Dictionary<Symbol, Procedure> macroTable;
|
||||||
|
private readonly IFileSystemAccessor fsAccessor;
|
||||||
|
|
||||||
|
public delegate IDictionary<Symbol, object> CreateSymbolTableDelegate(Interpreter interpreter);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Interpreter"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="environmentInitializers">Array of environment initializers</param>
|
||||||
|
/// <param name="fsAccessor">The file system accessor</param>
|
||||||
|
public Interpreter(IEnumerable<CreateSymbolTableDelegate> environmentInitializers = null, IFileSystemAccessor fsAccessor = null)
|
||||||
|
{
|
||||||
|
this.fsAccessor = fsAccessor;
|
||||||
|
if (this.fsAccessor == null)
|
||||||
|
{
|
||||||
|
this.fsAccessor = new ReadOnlyFileSystemAccessor();
|
||||||
|
}
|
||||||
|
|
||||||
|
// populate an empty environment for the initializer to potentially work with
|
||||||
|
this.environment = Environment.CreateEmpty();
|
||||||
|
this.macroTable = new Dictionary<Symbol, Procedure>();
|
||||||
|
|
||||||
|
environmentInitializers = environmentInitializers ?? new List<CreateSymbolTableDelegate>();
|
||||||
|
environmentInitializers = new CreateSymbolTableDelegate[] { Builtins.CreateBuiltins }.Concat(environmentInitializers);
|
||||||
|
|
||||||
|
foreach (CreateSymbolTableDelegate initializer in environmentInitializers)
|
||||||
|
{
|
||||||
|
this.environment = new Environment(initializer(this), this.environment);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var iniReader in GetInitializeFiles())
|
||||||
|
{
|
||||||
|
this.Evaluate(iniReader);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<TextReader> GetInitializeFiles()
|
||||||
|
{
|
||||||
|
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("init.ss"))
|
||||||
|
using (StreamReader reader = new StreamReader(stream))
|
||||||
|
{
|
||||||
|
yield return reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
string initFile = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), ".init.ss");
|
||||||
|
if (File.Exists(initFile))
|
||||||
|
{
|
||||||
|
using (var reader = new StreamReader(initFile))
|
||||||
|
{
|
||||||
|
yield return reader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IFileSystemAccessor FileSystemAccessor { get { return this.fsAccessor; } }
|
||||||
|
|
||||||
|
public Environment Environment { get { return this.environment; } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluate script from a input reader
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">the input source</param>
|
||||||
|
/// <returns>the value of the last expression</returns>
|
||||||
|
public EvaluationResult Evaluate(TextReader input)
|
||||||
|
{
|
||||||
|
InPort port = new InPort(input);
|
||||||
|
object res = null;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var expr = Expand(Read(port), environment, macroTable, true);
|
||||||
|
if (Symbol.EOF.Equals(expr))
|
||||||
|
{
|
||||||
|
return new EvaluationResult(null, res);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
res = EvaluateExpression(expr, environment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
return new EvaluationResult(e, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts the Read-Eval-Print loop
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">the input source</param>
|
||||||
|
/// <param name="output">the output target</param>
|
||||||
|
/// <param name="prompt">a string prompt to be printed before each evaluation</param>
|
||||||
|
/// <param name="headers">a head text to be printed at the beginning of the REPL</param>
|
||||||
|
public void REPL(TextReader input, TextWriter output, string prompt = null, string[] headers = null)
|
||||||
|
{
|
||||||
|
InPort port = new InPort(input);
|
||||||
|
|
||||||
|
if (headers != null)
|
||||||
|
{
|
||||||
|
foreach (var line in headers)
|
||||||
|
{
|
||||||
|
output.WriteLine(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object res = null;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(prompt) && output != null) output.Write(prompt);
|
||||||
|
var expr = Expand(Read(port), environment, macroTable, true);
|
||||||
|
if (Symbol.EOF.Equals(expr))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
res = EvaluateExpression(expr, environment);
|
||||||
|
if (output != null) output.WriteLine(Utils.PrintExpr(res));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a global symbol
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sym">the symbol</param>
|
||||||
|
/// <param name="val">the associated value</param>
|
||||||
|
public void DefineGlobal(Symbol sym, object val)
|
||||||
|
{
|
||||||
|
this.environment[sym] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reads an S-expression from the input source
|
||||||
|
/// </summary>
|
||||||
|
public static object Read(InPort port)
|
||||||
|
{
|
||||||
|
Func<object, object> readAhead = null;
|
||||||
|
readAhead = token =>
|
||||||
|
{
|
||||||
|
Symbol quote;
|
||||||
|
if (object.Equals(token, Symbol.EOF))
|
||||||
|
{
|
||||||
|
throw new SyntaxError("unexpected EOF");
|
||||||
|
}
|
||||||
|
else if (token is string)
|
||||||
|
{
|
||||||
|
string tokenStr = (string)token;
|
||||||
|
if (tokenStr == "(")
|
||||||
|
{
|
||||||
|
var L = new List<object>();
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
token = port.NextToken();
|
||||||
|
if (token is string && (string)token == ")")
|
||||||
|
{
|
||||||
|
return L;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
L.Add(readAhead(token));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (tokenStr == ")")
|
||||||
|
{
|
||||||
|
throw new SyntaxError("unexpected )");
|
||||||
|
}
|
||||||
|
else if (Symbol.QuotesMap.TryGetValue(tokenStr, out quote))
|
||||||
|
{
|
||||||
|
object quoted = Read(port);
|
||||||
|
return new List<object> { quote, quoted };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return ParseAtom(tokenStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new SyntaxError("unexpected token: " + token);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var token1 = port.NextToken();
|
||||||
|
return Symbol.EOF.Equals(token1) ? Symbol.EOF : readAhead(token1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates and expands the input s-expression
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="expression">expression to expand</param>
|
||||||
|
/// <param name="env">env used to evaluate the macro procedures</param>
|
||||||
|
/// <param name="macroTable">the macro definition table</param>
|
||||||
|
/// <param name="isTopLevel">whether the current expansion is at the top level</param>
|
||||||
|
/// <returns>the s-expression after validation and expansion</returns>
|
||||||
|
public static object Expand(object expression, Environment env, Dictionary<Symbol, Procedure> macroTable, bool isTopLevel = true)
|
||||||
|
{
|
||||||
|
Procedure procedure = null;
|
||||||
|
Func<object, bool, object> expand = null;
|
||||||
|
expand = (x, topLevel) =>
|
||||||
|
{
|
||||||
|
if (!(x is List<object>))
|
||||||
|
{
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<object> xs = (List<object>)x;
|
||||||
|
Utils.CheckSyntax(xs, xs.Count > 0);
|
||||||
|
|
||||||
|
if (Symbol.QUOTE.Equals(xs[0]))
|
||||||
|
{
|
||||||
|
Utils.CheckSyntax(xs, xs.Count == 2);
|
||||||
|
return xs;
|
||||||
|
}
|
||||||
|
else if (Symbol.IF.Equals(xs[0]))
|
||||||
|
{
|
||||||
|
if (xs.Count == 3)
|
||||||
|
{
|
||||||
|
xs.Add(None.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils.CheckSyntax(xs, xs.Count == 4);
|
||||||
|
return xs.Select(expr => expand(expr, false)).ToList();
|
||||||
|
}
|
||||||
|
else if (Symbol.SET.Equals(xs[0]))
|
||||||
|
{
|
||||||
|
Utils.CheckSyntax(xs, xs.Count == 3);
|
||||||
|
Utils.CheckSyntax(xs, xs[1] is Symbol, "can only set! a symbol");
|
||||||
|
return new List<object> { Symbol.SET, xs[1], expand(xs[2], false) };
|
||||||
|
}
|
||||||
|
else if (Symbol.DEFINE.Equals(xs[0]) || Symbol.DEFINE_MACRO.Equals(xs[0]))
|
||||||
|
{
|
||||||
|
Utils.CheckSyntax(xs, xs.Count >= 3);
|
||||||
|
Symbol def = (Symbol)xs[0];
|
||||||
|
object v = xs[1]; // sym or (sym+)
|
||||||
|
List<object> body = xs.Skip(2).ToList(); // expr or expr+
|
||||||
|
if (v is List<object>)
|
||||||
|
{
|
||||||
|
// (define (f args) body)
|
||||||
|
var args = (List<object>)v;
|
||||||
|
Utils.CheckSyntax(xs, args.Count > 0);
|
||||||
|
var f = args[0];
|
||||||
|
var @params = args.Skip(1).ToList();
|
||||||
|
return expand(new List<object> { def, f, Enumerable.Concat(new object[] { Symbol.LAMBDA, @params }, body).ToList() }, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Utils.CheckSyntax(xs, xs.Count == 3); // (define x expr)
|
||||||
|
Utils.CheckSyntax(xs, v is Symbol);
|
||||||
|
var expr = expand(xs[2], false);
|
||||||
|
if (Symbol.DEFINE_MACRO.Equals(def))
|
||||||
|
{
|
||||||
|
Utils.CheckSyntax(xs, topLevel, "define-macro is only allowed at the top level");
|
||||||
|
var proc = EvaluateExpression(expr, env);
|
||||||
|
Utils.CheckSyntax(xs, proc is Procedure, "macro must be a procedure");
|
||||||
|
macroTable[(Symbol)v] = (Procedure)proc;
|
||||||
|
return None.Instance;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// `define v expr`
|
||||||
|
return new List<object> { Symbol.DEFINE, v, expr /* after expansion */ };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Symbol.BEGIN.Equals(xs[0]))
|
||||||
|
{
|
||||||
|
if (xs.Count == 1) return None.Instance; // (begin) => None
|
||||||
|
|
||||||
|
// use the same topLevel so that `define-macro` is also allowed in a top-level `begin`.
|
||||||
|
return xs.Select(expr => expand(expr, topLevel)).ToList();
|
||||||
|
}
|
||||||
|
else if (Symbol.LAMBDA.Equals(xs[0]))
|
||||||
|
{
|
||||||
|
Utils.CheckSyntax(xs, xs.Count >= 3);
|
||||||
|
var vars = xs[1];
|
||||||
|
Utils.CheckSyntax(xs, vars is Symbol || (vars is List<object> && ((List<object>)vars).All(v => v is Symbol)), "illigal lambda argument");
|
||||||
|
|
||||||
|
object body;
|
||||||
|
if (xs.Count == 3)
|
||||||
|
{
|
||||||
|
// (lambda (...) expr)
|
||||||
|
body = xs[2];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// (lambda (...) expr+
|
||||||
|
body = Enumerable.Concat(new[] { Symbol.BEGIN }, xs.Skip(2)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new List<object> { Symbol.LAMBDA, vars, expand(body, false) };
|
||||||
|
}
|
||||||
|
else if (Symbol.QUASIQUOTE.Equals(xs[0]))
|
||||||
|
{
|
||||||
|
Utils.CheckSyntax(xs, xs.Count == 2);
|
||||||
|
return ExpandQuasiquote(xs[1]);
|
||||||
|
}
|
||||||
|
else if (xs[0] is Symbol && macroTable.TryGetValue((Symbol)xs[0], out procedure))
|
||||||
|
{
|
||||||
|
return expand(procedure.Call(xs.Skip(1).ToList()), topLevel);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return xs.Select(p => expand(p, false)).ToList();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return expand(expression, isTopLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluates an s-expression
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="expr">expression to be evaluated</param>
|
||||||
|
/// <param name="env">the environment in which the expression is evaluated</param>
|
||||||
|
/// <returns>the result of the evaluation</returns>
|
||||||
|
public static object EvaluateExpression(object expr, Environment env)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (expr is Symbol)
|
||||||
|
{
|
||||||
|
return env[(Symbol)expr];
|
||||||
|
}
|
||||||
|
else if (!(expr is List<object>))
|
||||||
|
{
|
||||||
|
return expr; // is a constant literal
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
List<object> exprList = (List<object>)expr;
|
||||||
|
if (Symbol.QUOTE.Equals(exprList[0]))
|
||||||
|
{
|
||||||
|
return exprList[1];
|
||||||
|
}
|
||||||
|
else if (Symbol.IF.Equals(exprList[0]))
|
||||||
|
{
|
||||||
|
var test = exprList[1];
|
||||||
|
var conseq = exprList[2];
|
||||||
|
var alt = exprList[3];
|
||||||
|
expr = ConvertToBool(EvaluateExpression(test, env)) ? conseq : alt;
|
||||||
|
}
|
||||||
|
else if (Symbol.DEFINE.Equals(exprList[0]))
|
||||||
|
{
|
||||||
|
var variable = (Symbol)exprList[1];
|
||||||
|
expr = exprList[2];
|
||||||
|
env[variable] = EvaluateExpression(expr, env);
|
||||||
|
return None.Instance; // TODO: what's the return type of define?
|
||||||
|
}
|
||||||
|
else if (Symbol.SET.Equals(exprList[0]))
|
||||||
|
{
|
||||||
|
var sym = (Symbol)exprList[1];
|
||||||
|
var containingEnv = env.TryFindContainingEnv(sym);
|
||||||
|
if (containingEnv == null)
|
||||||
|
{
|
||||||
|
throw new KeyNotFoundException("Symbol not defined: " + sym);
|
||||||
|
}
|
||||||
|
|
||||||
|
containingEnv[sym] = EvaluateExpression(exprList[2], env);
|
||||||
|
return None.Instance;
|
||||||
|
}
|
||||||
|
else if (Symbol.LAMBDA.Equals(exprList[0]))
|
||||||
|
{
|
||||||
|
Union<Symbol, List<Symbol>> parameters;
|
||||||
|
if (exprList[1] is Symbol)
|
||||||
|
{
|
||||||
|
parameters = new Union<Symbol, List<Symbol>>((Symbol)exprList[1]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
parameters = new Union<Symbol, List<Symbol>>(((List<object>)exprList[1]).Cast<Symbol>().ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Procedure(parameters, exprList[2], env);
|
||||||
|
}
|
||||||
|
else if (Symbol.BEGIN.Equals(exprList[0]))
|
||||||
|
{
|
||||||
|
for (int i = 1; i < exprList.Count - 1 /* don't eval last expr yet */; i++)
|
||||||
|
{
|
||||||
|
EvaluateExpression(exprList[i], env);
|
||||||
|
}
|
||||||
|
|
||||||
|
expr = exprList[exprList.Count - 1]; // tail call optimization
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// a procedure call
|
||||||
|
var rawProc = EvaluateExpression(exprList[0], env);
|
||||||
|
if (!(rawProc is ICallable))
|
||||||
|
{
|
||||||
|
throw new InvalidCastException(string.Format("Object is not callable: {0}", rawProc));
|
||||||
|
}
|
||||||
|
|
||||||
|
var args = exprList.Skip(1).Select(a => EvaluateExpression(a, env)).ToList();
|
||||||
|
if (rawProc is Procedure)
|
||||||
|
{
|
||||||
|
// Tail call optimization - instead of evaluating the procedure here which grows the
|
||||||
|
// stack by calling EvaluateExpression, we update the `expr` and `env` to be the
|
||||||
|
// body and the (params, args), and loop the evaluation from here.
|
||||||
|
var proc = (Procedure)rawProc;
|
||||||
|
expr = proc.Body;
|
||||||
|
env = Environment.FromVariablesAndValues(proc.Parameters, args, proc.Env);
|
||||||
|
}
|
||||||
|
else if (rawProc is NativeProcedure)
|
||||||
|
{
|
||||||
|
return ((NativeProcedure)rawProc).Call(args);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("unexpected implementation of ICallable: " + rawProc.GetType().Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsPair(object x)
|
||||||
|
{
|
||||||
|
return x is List<object> && ((List<object>)x).Count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object ExpandQuasiquote(object x)
|
||||||
|
{
|
||||||
|
if (!IsPair(x)) return new List<object> { Symbol.QUOTE, x };
|
||||||
|
var xs = (List<object>)x;
|
||||||
|
Utils.CheckSyntax(xs, !Symbol.UNQUOTE_SPLICING.Equals(xs[0]), "Cannot splice");
|
||||||
|
if (Symbol.UNQUOTE.Equals(xs[0]))
|
||||||
|
{
|
||||||
|
Utils.CheckSyntax(xs, xs.Count == 2);
|
||||||
|
return xs[1];
|
||||||
|
}
|
||||||
|
else if (IsPair(xs[0]) && Symbol.UNQUOTE_SPLICING.Equals(((List<object>)xs[0])[0]))
|
||||||
|
{
|
||||||
|
var x0 = (List<object>)xs[0];
|
||||||
|
Utils.CheckSyntax(x0, x0.Count == 2);
|
||||||
|
return new List<object> { Symbol.APPEND, x0[1], ExpandQuasiquote(xs.Skip(1).ToList()) };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new List<object> { Symbol.CONS, ExpandQuasiquote(xs[0]), ExpandQuasiquote(xs.Skip(1).ToList()) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object ParseAtom(string token)
|
||||||
|
{
|
||||||
|
int intVal;
|
||||||
|
double floatVal;
|
||||||
|
if (token == "#t")
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (token == "#f")
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (token[0] == '"')
|
||||||
|
{
|
||||||
|
return token.Substring(1, token.Length - 2);
|
||||||
|
}
|
||||||
|
else if (int.TryParse(token, out intVal))
|
||||||
|
{
|
||||||
|
return intVal;
|
||||||
|
}
|
||||||
|
else if (double.TryParse(token, out floatVal))
|
||||||
|
{
|
||||||
|
return floatVal;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Symbol.FromString(token); // a symbol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ConvertToBool(object val)
|
||||||
|
{
|
||||||
|
if (val is bool) return (bool)val;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct EvaluationResult
|
||||||
|
{
|
||||||
|
private readonly Exception error;
|
||||||
|
private readonly object result;
|
||||||
|
|
||||||
|
public EvaluationResult(Exception error, object result) : this()
|
||||||
|
{
|
||||||
|
this.error = error;
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Exception Error { get { return this.error; } }
|
||||||
|
|
||||||
|
public object Result { get { return this.result; } }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InPort
|
||||||
|
{
|
||||||
|
private const string tokenizer = @"^\s*(,@|[('`,)]|""(?:[\\].|[^\\""])*""|;.*|[^\s('""`,;)]*)(.*)";
|
||||||
|
|
||||||
|
private TextReader file;
|
||||||
|
private string line;
|
||||||
|
|
||||||
|
public InPort(TextReader file)
|
||||||
|
{
|
||||||
|
this.file = file;
|
||||||
|
this.line = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses and returns the next token. Returns <see cref="Symbol.EOF"/> if there's no more content to read.
|
||||||
|
/// </summary>
|
||||||
|
public object NextToken()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (this.line == string.Empty)
|
||||||
|
{
|
||||||
|
this.line = this.file.ReadLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.line == string.Empty)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (this.line == null)
|
||||||
|
{
|
||||||
|
return Symbol.EOF;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var res = Regex.Match(this.line, tokenizer);
|
||||||
|
var token = res.Groups[1].Value;
|
||||||
|
this.line = res.Groups[2].Value;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(token))
|
||||||
|
{
|
||||||
|
// 1st group is empty. All string falls into 2nd group. This usually means
|
||||||
|
// an error in the syntax, e.g., incomplete string "foo
|
||||||
|
var tmp = this.line;
|
||||||
|
this.line = string.Empty; // to continue reading next line
|
||||||
|
|
||||||
|
if (tmp.Trim() != string.Empty)
|
||||||
|
{
|
||||||
|
// this is a syntax error
|
||||||
|
Utils.CheckSyntax(tmp, false, "unexpected syntax");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(token) && !token.StartsWith(";"))
|
||||||
|
{
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
namespace Schemy
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Scheme symbol
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Symbols are interned so that symbols with the same name are actually of the same symbol object instance.
|
||||||
|
/// </remarks>
|
||||||
|
public class Symbol : IEquatable<Symbol>
|
||||||
|
{
|
||||||
|
private static readonly IDictionary<string, Symbol> table = new Dictionary<string, Symbol>();
|
||||||
|
public static readonly IReadOnlyDictionary<string, Symbol> QuotesMap = new Dictionary<string, Symbol>()
|
||||||
|
{
|
||||||
|
{ "'", Symbol.QUOTE },
|
||||||
|
{ "`", Symbol.QUASIQUOTE},
|
||||||
|
{ ",", Symbol.UNQUOTE},
|
||||||
|
{ ",@", Symbol.UNQUOTE_SPLICING},
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly string symbol;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Symbol"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sym">The symbol</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is private and the users should call <see cref="FromString"/> to instantiate a symbol object.
|
||||||
|
/// </remarks>
|
||||||
|
private Symbol(string sym)
|
||||||
|
{
|
||||||
|
this.symbol = sym;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string AsString
|
||||||
|
{
|
||||||
|
get { return this.symbol; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the interned symbol
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sym">The symbol name</param>
|
||||||
|
/// <returns>the symbol instance</returns>
|
||||||
|
public static Symbol FromString(string sym)
|
||||||
|
{
|
||||||
|
Symbol res;
|
||||||
|
if (!table.TryGetValue(sym, out res))
|
||||||
|
{
|
||||||
|
table[sym] = new Symbol(sym);
|
||||||
|
}
|
||||||
|
|
||||||
|
return table[sym];
|
||||||
|
}
|
||||||
|
|
||||||
|
#region wellknown symbols
|
||||||
|
public static Symbol IF { get { return Symbol.FromString("if"); } }
|
||||||
|
public static Symbol QUOTE { get { return Symbol.FromString("quote"); } }
|
||||||
|
public static Symbol SET { get { return Symbol.FromString("set!"); } }
|
||||||
|
public static Symbol DEFINE { get { return Symbol.FromString("define"); } }
|
||||||
|
public static Symbol LAMBDA { get { return Symbol.FromString("lambda"); } }
|
||||||
|
public static Symbol BEGIN { get { return Symbol.FromString("begin"); } }
|
||||||
|
public static Symbol DEFINE_MACRO { get { return Symbol.FromString("define-macro"); } }
|
||||||
|
public static Symbol QUASIQUOTE { get { return Symbol.FromString("quasiquote"); } }
|
||||||
|
public static Symbol UNQUOTE { get { return Symbol.FromString("unquote"); } }
|
||||||
|
public static Symbol UNQUOTE_SPLICING { get { return Symbol.FromString("unquote-splicing"); } }
|
||||||
|
public static Symbol EOF { get { return Symbol.FromString("#<eof-object>"); } }
|
||||||
|
public static Symbol APPEND { get { return Symbol.FromString("append"); } }
|
||||||
|
public static Symbol CONS { get { return Symbol.FromString("cons"); } }
|
||||||
|
#endregion wellknown symbols
|
||||||
|
|
||||||
|
#region object implementations
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
if (obj == null) return false;
|
||||||
|
if (obj is Symbol)
|
||||||
|
{
|
||||||
|
return object.Equals(this.symbol, ((Symbol)obj).symbol);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return string.Format("'{0}", this.symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return this.symbol.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(Symbol other)
|
||||||
|
{
|
||||||
|
return ((object)this).Equals(other);
|
||||||
|
}
|
||||||
|
#endregion object implementations
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
namespace Schemy
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
public static class Utils
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Checks the arity of input arguments of a procedure
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">The arguments.</param>
|
||||||
|
/// <param name="acceptableArities">The acceptable arity.</param>
|
||||||
|
/// <exception cref="SyntaxError">thrown when that number of args doesn't match the expected arity.</exception>
|
||||||
|
public static void CheckArity(List<object> args, params int[] acceptableArities)
|
||||||
|
{
|
||||||
|
if (!acceptableArities.Contains(args.Count))
|
||||||
|
{
|
||||||
|
throw new SyntaxError(string.Format("Arity mismatch. Expecting {0}, Got {1}", string.Join(" or ", acceptableArities), args.Count));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Throws <see cref="SyntaxError"/> if the syntax check is not successful, and prints the expression for diagnostics.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="expr">The expr that's being checked</param>
|
||||||
|
/// <param name="success">if the syntax check was successful</param>
|
||||||
|
/// <param name="msg">The error message</param>
|
||||||
|
/// <exception cref="SyntaxError">thrown when the syntax check was failed.</exception>
|
||||||
|
public static void CheckSyntax(object expr, bool success, string msg = null)
|
||||||
|
{
|
||||||
|
msg = msg ?? "Syntax error";
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
throw new SyntaxError(string.Format("{0}: {1}", msg, Utils.PrintExpr(expr)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts the type of the input to the desired type
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">desired target type</typeparam>
|
||||||
|
/// <param name="val">The input value.</param>
|
||||||
|
/// <returns>the object of the target type</returns>
|
||||||
|
/// <exception cref="InvalidOperationException">thrown when the conversion is not possible</exception>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is needed because the regular casting can't handle some implicit convert when going through boxing/unboxing, e.g., int to object to double.
|
||||||
|
/// </remarks>
|
||||||
|
public static T ConvertType<T>(object val)
|
||||||
|
{
|
||||||
|
if (val is T) return (T)val;
|
||||||
|
|
||||||
|
// object x = 2;
|
||||||
|
// double y = (double)x; // <-- this would fail.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return (T)System.Convert.ChangeType(val, typeof(T));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(string.Format("Cannot convert {0} to type {1}", Utils.PrintExpr(val), typeof(T).Name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string PrintExpr(object x)
|
||||||
|
{
|
||||||
|
if (x is bool)
|
||||||
|
{
|
||||||
|
return (bool)x ? "#t" : "#f";
|
||||||
|
}
|
||||||
|
else if (x is Symbol) return ((Symbol)x).AsString;
|
||||||
|
else if (x is string) return string.Format(@"""{0}""", x);
|
||||||
|
else if (x is List<object>) return string.Format("({0})", string.Join(" ", ((List<object>)x).Select(a => PrintExpr(a))));
|
||||||
|
else if (x == null) return string.Empty;
|
||||||
|
else return x.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a binary operator (function) to the variadic version.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Given a summing function `sum(x, y) => result`. It creates a variadic version: `sum(x, y, ...) => result`.
|
||||||
|
/// </remarks>
|
||||||
|
public static Func<List<object>, object> MakeVariadic(Func<object, object, object> func)
|
||||||
|
{
|
||||||
|
return args => args.Aggregate(func);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
(define-macro let
|
||||||
|
(lambda args
|
||||||
|
(define specs (car args)) ; ( (var1 val1), ... )
|
||||||
|
(define bodies (cdr args)) ; (expr1 ...)
|
||||||
|
(if (null? specs)
|
||||||
|
`((lambda () ,@bodies))
|
||||||
|
(begin
|
||||||
|
(define spec1 (car specs)) ; (var1 val1)
|
||||||
|
(define spec_rest (cdr specs)) ; ((var2 val2) ...)
|
||||||
|
(define inner `((lambda ,(list (car spec1)) ,@bodies) ,(car (cdr spec1))))
|
||||||
|
`(let ,spec_rest ,inner)))))
|
||||||
|
|
||||||
|
(define-macro cond
|
||||||
|
(lambda args
|
||||||
|
(if (= 0 (length args)) ''()
|
||||||
|
(begin
|
||||||
|
(define first (car args))
|
||||||
|
(define rest (cdr args))
|
||||||
|
(define test1 (if (equal? (car first) 'else) '#t (car first)))
|
||||||
|
(define expr1 (car (cdr first)))
|
||||||
|
`(if ,test1 ,expr1
|
||||||
|
(cond ,@rest))))))
|
|
@ -0,0 +1,69 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{E54139B7-CB81-4883-B8CD-40BAB5420EB8}</ProjectGuid>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
|
<RootNamespace>Schemy</RootNamespace>
|
||||||
|
<AssemblyName>schemy</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<StartupObject />
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
|
<Reference Include="Microsoft.CSharp" />
|
||||||
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Net.Http" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Builtins.cs" />
|
||||||
|
<Compile Include="CommonTypes.cs" />
|
||||||
|
<Compile Include="Env.cs" />
|
||||||
|
<Compile Include="FileSystemAccessor.cs" />
|
||||||
|
<Compile Include="Procedure.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="Schemy.cs" />
|
||||||
|
<Compile Include="Symbol.cs" />
|
||||||
|
<Compile Include="Utils.cs" />
|
||||||
|
<EmbeddedResource Include="init.ss"><LogicalName>init.ss</LogicalName></EmbeddedResource>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
<Target Name="BeforeBuild">
|
||||||
|
</Target>
|
||||||
|
<Target Name="AfterBuild">
|
||||||
|
</Target>
|
||||||
|
-->
|
||||||
|
</Project>
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
namespace test
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using Schemy;
|
||||||
|
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var interpreter = new Interpreter(fsAccessor: new ReadOnlyFileSystemAccessor());
|
||||||
|
using (var reader = new StreamReader(File.OpenRead("tests.ss")))
|
||||||
|
{
|
||||||
|
var result = interpreter.Evaluate(reader);
|
||||||
|
if (result.Error != null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(string.Format("Test Error: {0}", result.Error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("Tests were successful");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{4A62A84F-58AA-4D1C-AA7C-D3CDF0C3FFA6}</ProjectGuid>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
|
<RootNamespace>test</RootNamespace>
|
||||||
|
<AssemblyName>test</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
|
<PublishUrl>publish\</PublishUrl>
|
||||||
|
<Install>true</Install>
|
||||||
|
<InstallFrom>Disk</InstallFrom>
|
||||||
|
<UpdateEnabled>false</UpdateEnabled>
|
||||||
|
<UpdateMode>Foreground</UpdateMode>
|
||||||
|
<UpdateInterval>7</UpdateInterval>
|
||||||
|
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
|
||||||
|
<UpdatePeriodically>false</UpdatePeriodically>
|
||||||
|
<UpdateRequired>false</UpdateRequired>
|
||||||
|
<MapFileExtensions>true</MapFileExtensions>
|
||||||
|
<ApplicationRevision>0</ApplicationRevision>
|
||||||
|
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
|
||||||
|
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||||
|
<UseApplicationTrust>false</UseApplicationTrust>
|
||||||
|
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
|
<Reference Include="Microsoft.CSharp" />
|
||||||
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Net.Http" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Program.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\schemy\schemy.csproj">
|
||||||
|
<Project>{e54139b7-cb81-4883-b8cd-40bab5420eb8}</Project>
|
||||||
|
<Name>schemy</Name>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<BootstrapperPackage Include=".NETFramework,Version=v4.5.2">
|
||||||
|
<Visible>False</Visible>
|
||||||
|
<ProductName>Microsoft .NET Framework 4.5.2 %28x86 and x64%29</ProductName>
|
||||||
|
<Install>true</Install>
|
||||||
|
</BootstrapperPackage>
|
||||||
|
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
|
||||||
|
<Visible>False</Visible>
|
||||||
|
<ProductName>.NET Framework 3.5 SP1</ProductName>
|
||||||
|
<Install>false</Install>
|
||||||
|
</BootstrapperPackage>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Properties\" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<PostBuildEvent>copy $(ProjectDir)\tests.ss $(TargetDir)\tests.ss</PostBuildEvent>
|
||||||
|
</PropertyGroup>
|
||||||
|
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||||
|
Other similar extension points exist, see Microsoft.Common.targets.
|
||||||
|
<Target Name="BeforeBuild">
|
||||||
|
</Target>
|
||||||
|
<Target Name="AfterBuild">
|
||||||
|
</Target>
|
||||||
|
-->
|
||||||
|
</Project>
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<PropertyGroup>
|
||||||
|
<PublishUrlHistory>publish\</PublishUrlHistory>
|
||||||
|
<InstallUrlHistory />
|
||||||
|
<SupportUrlHistory />
|
||||||
|
<UpdateUrlHistory />
|
||||||
|
<BootstrapperUrlHistory />
|
||||||
|
<ErrorReportUrlHistory />
|
||||||
|
<FallbackCulture>en-US</FallbackCulture>
|
||||||
|
<VerifyUploadedFiles>false</VerifyUploadedFiles>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio 14
|
||||||
|
VisualStudioVersion = 14.0.25420.1
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "test", "test.csproj", "{4A62A84F-58AA-4D1C-AA7C-D3CDF0C3FFA6}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{4A62A84F-58AA-4D1C-AA7C-D3CDF0C3FFA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4A62A84F-58AA-4D1C-AA7C-D3CDF0C3FFA6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4A62A84F-58AA-4D1C-AA7C-D3CDF0C3FFA6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4A62A84F-58AA-4D1C-AA7C-D3CDF0C3FFA6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
|
@ -0,0 +1,147 @@
|
||||||
|
;; ============
|
||||||
|
;; DEFINE TESTS
|
||||||
|
;; ============
|
||||||
|
|
||||||
|
;; ------------
|
||||||
|
;; Simple tests
|
||||||
|
;; ------------
|
||||||
|
(define simple-tests
|
||||||
|
(list
|
||||||
|
`(,(+ 1 2) 3)
|
||||||
|
`(,(- 2 1) 1)
|
||||||
|
`(,(* 2 3) 6)
|
||||||
|
`(,(/ 4 3) 1)
|
||||||
|
`(,(= 1 1) #t)
|
||||||
|
`(,(= 1 2) #f)
|
||||||
|
`(,(< 1 2) #t)
|
||||||
|
`(,(> 1 2) #f)
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
;; -----------
|
||||||
|
;; Test syntax
|
||||||
|
;; -----------
|
||||||
|
(define (test-syntax)
|
||||||
|
(define x 1)
|
||||||
|
(assert (= x 1))
|
||||||
|
|
||||||
|
(define f (lambda (x) (+ x 1)))
|
||||||
|
(assert (= 2 (f 1)))
|
||||||
|
|
||||||
|
;; Tests lambda definition and lexical scoping
|
||||||
|
;; `create-student` implements a minimum "struct" by using lexical variable
|
||||||
|
;; scoping. It is a function that returns a list of three functions:
|
||||||
|
;; 1. a function that returns the (name age)
|
||||||
|
;; 2. a function that sets the student's name
|
||||||
|
;; 3. a function that sets the student's age
|
||||||
|
(define (create-student name age)
|
||||||
|
(define (get-student) (list name age))
|
||||||
|
(define (set-name! v) (set! name v))
|
||||||
|
(define (set-age! v) (set! age v))
|
||||||
|
(list get-student set-name! set-age!))
|
||||||
|
|
||||||
|
(define john (create-student "john" 18))
|
||||||
|
(define mike (create-student "mike" 22))
|
||||||
|
|
||||||
|
(assert (equal? '("john" 18) ((list-ref john 0))))
|
||||||
|
((list-ref john 2) 19) ; set john's age to 19
|
||||||
|
(assert (equal? '("john" 19) ((list-ref john 0))))
|
||||||
|
(assert (equal? '("mike" 22) ((list-ref mike 0))))
|
||||||
|
|
||||||
|
;; Test proper tail recursion
|
||||||
|
(define (sum-up-to n acc)
|
||||||
|
(if (= n 0) acc
|
||||||
|
(sum-up-to (- n 1) (+ acc n))))
|
||||||
|
(assert (= 1250025000 (sum-up-to 50000 0)) "test proper tail recursion")
|
||||||
|
) ; test-syntax
|
||||||
|
|
||||||
|
|
||||||
|
;; ----------------------------
|
||||||
|
;; Test list related operations
|
||||||
|
;; ----------------------------
|
||||||
|
(define (test-list)
|
||||||
|
; test list is correctly constructed
|
||||||
|
; test `car` and `cdr`
|
||||||
|
(define ls (list 1 2 3 4))
|
||||||
|
(assert (list? ls))
|
||||||
|
(assert (not (list? 1)))
|
||||||
|
(assert (= 4 (length ls)))
|
||||||
|
(assert (= (car ls) 1))
|
||||||
|
(assert (= (car (cdr ls)) 2))
|
||||||
|
(assert (= (car (cdr (cdr ls))) 3))
|
||||||
|
(assert (= (car (cdr (cdr (cdr ls)))) 4))
|
||||||
|
|
||||||
|
; test list literal
|
||||||
|
(define ls2 '(1 2 3 4))
|
||||||
|
|
||||||
|
; test list operations
|
||||||
|
(assert (equal? ls ls2))
|
||||||
|
(assert (equal? ls (range 1 5)))
|
||||||
|
(assert (null? (list)))
|
||||||
|
(assert (not (null? (list 1))))
|
||||||
|
(assert (= 0 (length (list))))
|
||||||
|
|
||||||
|
; test list reversion
|
||||||
|
(define lsr '(4 3 2 1))
|
||||||
|
(assert (equal? (reverse ls) lsr))
|
||||||
|
|
||||||
|
; test `map`
|
||||||
|
(define (double x) (* x 2))
|
||||||
|
(assert (equal? `(2 4 6 8) (map double ls)))
|
||||||
|
) ; test-list
|
||||||
|
|
||||||
|
|
||||||
|
;; ----------
|
||||||
|
;; Test macro
|
||||||
|
;; ----------
|
||||||
|
(define-macro let
|
||||||
|
(lambda args
|
||||||
|
(define specs (car args)) ; ((var1 val1), ...)
|
||||||
|
(define bodies (cdr args)) ; (expr1 ...)
|
||||||
|
(if (null? specs)
|
||||||
|
`((lambda () ,@bodies))
|
||||||
|
(begin
|
||||||
|
(define spec1 (car specs)) ; (var1 val1)
|
||||||
|
(define spec_rest (cdr specs)) ; ((var2 val2) ...)
|
||||||
|
(define inner `((lambda ,(list (car spec1)) ,@bodies) ,(car (cdr spec1))))
|
||||||
|
`(let ,spec_rest ,inner)))))
|
||||||
|
|
||||||
|
(define (test-macro)
|
||||||
|
; test the `let` macro
|
||||||
|
(define x
|
||||||
|
(let ((a 4)
|
||||||
|
(b (+ 2 3)))
|
||||||
|
(* a b)))
|
||||||
|
(assert (= 20 x)))
|
||||||
|
|
||||||
|
|
||||||
|
;; =========
|
||||||
|
;; RUN TESTS
|
||||||
|
;; =========
|
||||||
|
|
||||||
|
;; run tests in ((actual, expected) ... )
|
||||||
|
(define (test specs)
|
||||||
|
(if (null? specs)
|
||||||
|
#t
|
||||||
|
(begin
|
||||||
|
(define head (car specs))
|
||||||
|
(assert (equal? (car head) (car (cdr head))))
|
||||||
|
(test (cdr specs)))))
|
||||||
|
(test simple-tests)
|
||||||
|
|
||||||
|
(test-list)
|
||||||
|
(test-syntax)
|
||||||
|
(test-macro)
|
||||||
|
|
||||||
|
|
||||||
|
;; =======================
|
||||||
|
;; Interpreter integration
|
||||||
|
;; =======================
|
||||||
|
|
||||||
|
; Test those global variables are accessible from interpreter environment table
|
||||||
|
; and that the interpreter can invoke the procedure to get the correct result.
|
||||||
|
(define ANSWER-TO-THE-ULTIMATE-QUESTION-OF-LIFE-UNIVERSE-AND-EVERYTHING 42)
|
||||||
|
(define (TIMES-TWO x) (* 2 x))
|
||||||
|
|
||||||
|
; Test that the last value is the return result of the interpreter
|
||||||
|
"good bye"
|
|
@ -0,0 +1,45 @@
|
||||||
|
#|
|
||||||
|
This script evaluates a script file to transform input file content. The transformed output
|
||||||
|
is displayed to stdout.
|
||||||
|
|
||||||
|
This is currently broken because racket IO APIs doesn't strip BOM at the beginning of the file
|
||||||
|
|#
|
||||||
|
|
||||||
|
#lang at-exp racket
|
||||||
|
|
||||||
|
(require web-server/templates
|
||||||
|
racket/cmdline)
|
||||||
|
|
||||||
|
(define template-file (make-parameter ""))
|
||||||
|
(define input-file (make-parameter ""))
|
||||||
|
|
||||||
|
(command-line
|
||||||
|
#:once-each
|
||||||
|
[("-t" "--template") template
|
||||||
|
"template file to use. `FILENAME` and `INPUT` variables are available to the template"
|
||||||
|
(template-file template)]
|
||||||
|
#:args (input)
|
||||||
|
(input-file input))
|
||||||
|
|
||||||
|
#|
|
||||||
|
(define (read-content fn)
|
||||||
|
(define lines (port->lines (open-input-file #:mode 'text (input-file))))
|
||||||
|
(string-join lines "\n"))
|
||||||
|
|#
|
||||||
|
|
||||||
|
(define (read-content fn)
|
||||||
|
(port->string (open-input-file fn))
|
||||||
|
)
|
||||||
|
|
||||||
|
(define INPUT (read-content (input-file)))
|
||||||
|
(define FILENAME (path->string (file-name-from-path (input-file))))
|
||||||
|
|
||||||
|
(define ns (make-base-namespace))
|
||||||
|
(namespace-set-variable-value! 'INPUT INPUT #f ns)
|
||||||
|
(namespace-set-variable-value! 'FILENAME FILENAME #f ns)
|
||||||
|
|
||||||
|
(void
|
||||||
|
(write-string
|
||||||
|
(eval
|
||||||
|
(read
|
||||||
|
(open-input-file (template-file))) ns)))
|
Loading…
Reference in New Issue