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
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## 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
|
||||
bin/
|
||||
obj/
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#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/
|
||||
~$*
|
||||
*.swp
|
||||
*~
|
||||
*.dbmdl
|
||||
*.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
|
||||
*.nupkg
|
||||
|
|
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
|
||||
|
||||
|
@ -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/).
|
||||
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.
|
||||
|
||||
|
||||
|
||||
[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