// 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; /// /// Extend the interpreter with essential builtin functionalities /// public class Builtins { public static IDictionary CreateBuiltins(Interpreter interpreter) { var builtins = new Dictionary(); 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((x, y) => x == y, "="); builtins[Symbol.FromString("<")] = NativeProcedure.Create((x, y) => x < y, "<"); builtins[Symbol.FromString("<=")] = NativeProcedure.Create((x, y) => x <= y, "<="); builtins[Symbol.FromString(">")] = NativeProcedure.Create((x, y) => x > y, ">"); builtins[Symbol.FromString(">=")] = NativeProcedure.Create((x, y) => x >= y, ">="); builtins[Symbol.FromString("eq?")] = NativeProcedure.Create((x, y) => object.ReferenceEquals(x, y), "eq?"); builtins[Symbol.FromString("equal?")] = NativeProcedure.Create(EqualImpl, "equal?"); builtins[Symbol.FromString("boolean?")] = NativeProcedure.Create(x => x is bool, "boolean?"); builtins[Symbol.FromString("num?")] = NativeProcedure.Create(x => x is int || x is double, "num?"); builtins[Symbol.FromString("string?")] = NativeProcedure.Create(x => x is string, "string?"); builtins[Symbol.FromString("symbol?")] = NativeProcedure.Create(x => x is Symbol, "symbol?"); builtins[Symbol.FromString("list?")] = NativeProcedure.Create(x => x is List, "list?"); builtins[Symbol.FromString("map")] = NativeProcedure.Create, List>((func, ls) => ls.Select(x => func.Call(new List { x })).ToList()); builtins[Symbol.FromString("reverse")] = NativeProcedure.Create, List>(ls => ls.Reverse().ToList()); builtins[Symbol.FromString("range")] = new NativeProcedure(RangeImpl, "range"); builtins[Symbol.FromString("apply")] = NativeProcedure.Create, object>((proc, args) => proc.Call(args), "apply"); builtins[Symbol.FromString("list")] = new NativeProcedure(args => args, "list"); builtins[Symbol.FromString("list-ref")] = NativeProcedure.Create, int, object>((ls, idx) => ls[idx]); builtins[Symbol.FromString("length")] = NativeProcedure.Create, int>(list => list.Count, "length"); builtins[Symbol.FromString("car")] = NativeProcedure.Create, object>(args => args[0], "car"); builtins[Symbol.FromString("cdr")] = NativeProcedure.Create, List>(args => args.Skip(1).ToList(), "cdr"); builtins[Symbol.CONS] = NativeProcedure.Create, List>((x, ys) => Enumerable.Concat(new[] { x }, ys).ToList(), "cons"); builtins[Symbol.FromString("not")] = NativeProcedure.Create(x => !x, "not"); builtins[Symbol.APPEND] = NativeProcedure.Create, List, List>((l1, l2) => Enumerable.Concat(l1, l2).ToList(), "append"); builtins[Symbol.FromString("null")] = NativeProcedure.Create(() => (object)null, "null"); builtins[Symbol.FromString("null?")] = NativeProcedure.Create(x => x is List && ((List)x).Count == 0, "null?"); builtins[Symbol.FromString("assert")] = new NativeProcedure(AssertImpl, "assert"); builtins[Symbol.FromString("load")] = NativeProcedure.Create(filename => LoadImpl(interpreter, filename), "load"); return builtins; } #region Builtin Implementations private static List RangeImpl(List 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(); 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 args) { Utils.CheckArity(args, 1, 2); string msg = "Assertion failed"; msg += args.Count > 1 ? ": " + Utils.ConvertType(args[1]) : string.Empty; bool pred = Utils.ConvertType(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 && y is IList) { var x2 = (IList)x; var y2 = (IList)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 } }