mirror of
https://github.com/kanaka/mal.git
synced 2024-11-11 08:56:41 +03:00
425305df78
Optimizations: * In Environment, skip checking unused local slots if we’ve switched over to the general map. * Mark as much as possible with “final” and “private”, and build with -whole-module-optimization, per https://developer.apple.com/swift/blog/?id=27. * Refactor to include alternate types.swift with an implementation based on “enum”. Add Makefile variable allowing us to switch between the two so that we can compare the results. (At the time of this writing, using the class-based implementation is better in terms of both size and speed.) Swift 1.2: * Use the form of “if-let-as” that allows us to unwrap multiple optionals at once. * Use Swift’s as? rather than our own predicates after determining that the former did not incur a performance hit. Swift 2.0: * Remove some Array conversions where Foundation/Cocoa is now declared to return arrays of the desired type. * println -> print. * print() -> print("") * Remove some NSError* parameters; changed to do/try/catch. * Use Swift exception handling rather than tunneling that information in MalError. * Use `guard` statements where it makes sense. Especially `guard let a = b as? c` statements in order to reduce subsequent forced unwrapping. * Changed count(str) -> str.characters.count. * Changed Swift.reduce(coll, ...) -> coll.reduce(...). * Changed reverse(coll) -> coll.reverse(). * Changed use of .allZeros -> default OptionSet c'tor. * Changed Printable -> CustomStringConvertible. * Changed Sequence.extend -> Sequence.appendContentsOf * Changed String.join -> Sequence.joinWithSeparator * Changed advance(index, delta) -> index.advancedBy(delta) * Addressed change in function parameter name requirements. * Added and used substring(s, begin, end). * Changed “for ch in str” to “for ch in str.characters” * Changed some switch/case statements to briefer if/case statements. * Slices are no longer necessarily 0-based. * Sprinkle in some @noescapes. * Search for the most recent swiftc compiler to use if Xcode and Xcode-beta are both installed. Other: * Delete debugger symbols in `make clean`. * Rebuild if Makefile is changed.
171 lines
4.7 KiB
Swift
171 lines
4.7 KiB
Swift
//******************************************************************************
|
|
// MAL - step 2 - eval
|
|
//******************************************************************************
|
|
// This file is automatically generated from templates/step.swift. Rather than
|
|
// editing it directly, it's probably better to edit templates/step.swift and
|
|
// regenerate this file. Otherwise, your change might be lost if/when someone
|
|
// else performs that process.
|
|
//******************************************************************************
|
|
|
|
import Foundation
|
|
|
|
// Parse the string into an AST.
|
|
//
|
|
private func READ(str: String) throws -> MalVal {
|
|
return try read_str(str)
|
|
}
|
|
|
|
// Perform a simple evaluation of the `ast` object. If it's a symbol,
|
|
// dereference it and return its value. If it's a collection, call EVAL on all
|
|
// elements (or just the values, in the case of the hashmap). Otherwise, return
|
|
// the object unchanged.
|
|
//
|
|
private func eval_ast(ast: MalVal, _ env: Environment) throws -> MalVal {
|
|
if let symbol = as_symbolQ(ast) {
|
|
guard let val = env.get(symbol) else {
|
|
try throw_error("'\(symbol)' not found") // Specific text needed to match MAL unit tests
|
|
}
|
|
return val
|
|
}
|
|
if let list = as_listQ(ast) {
|
|
var result = [MalVal]()
|
|
result.reserveCapacity(Int(list.count))
|
|
for item in list {
|
|
let eval = try EVAL(item, env)
|
|
result.append(eval)
|
|
}
|
|
return make_list(result)
|
|
}
|
|
if let vec = as_vectorQ(ast) {
|
|
var result = [MalVal]()
|
|
result.reserveCapacity(Int(vec.count))
|
|
for item in vec {
|
|
let eval = try EVAL(item, env)
|
|
result.append(eval)
|
|
}
|
|
return make_vector(result)
|
|
}
|
|
if let hash = as_hashmapQ(ast) {
|
|
var result = [MalVal]()
|
|
result.reserveCapacity(Int(hash.count) * 2)
|
|
for (k, v) in hash {
|
|
let new_v = try EVAL(v, env)
|
|
result.append(k)
|
|
result.append(new_v)
|
|
}
|
|
return make_hashmap(result)
|
|
}
|
|
return ast
|
|
}
|
|
|
|
// Walk the AST and completely evaluate it, handling macro expansions, special
|
|
// forms and function calls.
|
|
//
|
|
private func EVAL(ast: MalVal, _ env: Environment) throws -> MalVal {
|
|
|
|
if !is_list(ast) {
|
|
|
|
// Not a list -- just evaluate and return.
|
|
|
|
let answer = try eval_ast(ast, env)
|
|
return answer
|
|
}
|
|
|
|
// Special handling if it's a list.
|
|
|
|
let list = as_list(ast)
|
|
|
|
if list.isEmpty {
|
|
return ast
|
|
}
|
|
|
|
// Standard list to be applied. Evaluate all the elements first.
|
|
|
|
let eval = try eval_ast(ast, env)
|
|
|
|
// The result had better be a list and better be non-empty.
|
|
|
|
let eval_list = as_list(eval)
|
|
if eval_list.isEmpty {
|
|
return eval
|
|
}
|
|
|
|
// Get the first element of the list and execute it.
|
|
|
|
let first = eval_list.first()
|
|
let rest = as_sequence(eval_list.rest())
|
|
|
|
if let fn = as_builtinQ(first) {
|
|
let answer = try fn.apply(rest)
|
|
return answer
|
|
}
|
|
|
|
// The first element wasn't a function to be executed. Return an
|
|
// error saying so.
|
|
|
|
try throw_error("first list item does not evaluate to a function: \(first)")
|
|
}
|
|
|
|
// Convert the value into a human-readable string for printing.
|
|
//
|
|
private func PRINT(exp: MalVal) -> String {
|
|
return pr_str(exp, true)
|
|
}
|
|
|
|
// Perform the READ and EVAL steps. Useful for when you don't care about the
|
|
// printable result.
|
|
//
|
|
private func RE(text: String, _ env: Environment) -> MalVal? {
|
|
if !text.isEmpty {
|
|
do {
|
|
let ast = try READ(text)
|
|
do {
|
|
return try EVAL(ast, env)
|
|
} catch let error as MalException {
|
|
print("Error evaluating input: \(error)")
|
|
} catch {
|
|
print("Error evaluating input: \(error)")
|
|
}
|
|
} catch let error as MalException {
|
|
print("Error parsing input: \(error)")
|
|
} catch {
|
|
print("Error parsing input: \(error)")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Perform the full READ/EVAL/PRINT, returning a printable string.
|
|
//
|
|
private func REP(text: String, _ env: Environment) -> String? {
|
|
let exp = RE(text, env)
|
|
if exp == nil { return nil }
|
|
return PRINT(exp!)
|
|
}
|
|
|
|
// Perform the full REPL.
|
|
//
|
|
private func REPL(env: Environment) {
|
|
while true {
|
|
if let text = _readline("user> ") {
|
|
if let output = REP(text, env) {
|
|
print("\(output)")
|
|
}
|
|
} else {
|
|
print("")
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
let env = Environment(outer: nil)
|
|
|
|
load_history_file()
|
|
load_builtins(env)
|
|
|
|
REPL(env)
|
|
|
|
save_history_file()
|
|
}
|