Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
oliver-keyspace committed Jan 4, 2023
0 parents commit 16dc9e1
Show file tree
Hide file tree
Showing 10 changed files with 707 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
DerivedData/
.swiftpm
.netrc
59 changes: 59 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"pins" : [
{
"identity" : "bigint",
"kind" : "remoteSourceControl",
"location" : "https://github.com/attaswift/BigInt",
"state" : {
"revision" : "0ed110f7555c34ff468e72e1686e59721f2b0da6",
"version" : "5.3.0"
}
},
{
"identity" : "binarycodable",
"kind" : "remoteSourceControl",
"location" : "https://github.com/jverkoey/BinaryCodable",
"state" : {
"revision" : "aebaa30f1b73c5c45c374d251029b8bd68bde755",
"version" : "0.3.1"
}
},
{
"identity" : "swift-bignum",
"kind" : "remoteSourceControl",
"location" : "https://github.com/dankogai/swift-bignum",
"state" : {
"revision" : "c7e3e4374d9b956e5e9e9014952a981fcf2aca2b",
"version" : "5.2.5"
}
},
{
"identity" : "swift-bls-signatures",
"kind" : "remoteSourceControl",
"location" : "git@github.com:keyspacewallet/swift-bls-signatures.git",
"state" : {
"revision" : "e107b5caca1ee4c06591b9db512bd2c5357b94b6",
"version" : "0.0.3"
}
},
{
"identity" : "swift-clvm",
"kind" : "remoteSourceControl",
"location" : "git@github.com:keyspaceapp/swift-clvm.git",
"state" : {
"revision" : "c1d09e8d986e253bc477b8a810ad6e9adcffcb4a",
"version" : "0.0.5"
}
},
{
"identity" : "swift-numerics",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-numerics",
"state" : {
"revision" : "0a5bc04095a675662cf24757cc0640aa2204253b",
"version" : "1.0.2"
}
}
],
"version" : 2
}
29 changes: 29 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// swift-tools-version: 5.7

import PackageDescription

let package = Package(
name: "swift-clvm-tools",
platforms: [
.iOS(.v15),
.macOS(.v10_15)
],
products: [
.library(
name: "CLVMTools",
targets: ["CLVMTools"])
],
dependencies: [
.package(url: "git@github.com:keyspaceapp/swift-clvm.git", from: "0.0.5")
],
targets: [
.target(
name: "CLVMTools",
dependencies: [
.product(name: "CLVM", package: "swift-clvm", condition: nil),
]),
.testTarget(
name: "CLVMToolsTests",
dependencies: ["CLVMTools"]),
]
)
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# swift-clvm-tools

Chialisp compiler and other CLVM related tools.
268 changes: 268 additions & 0 deletions Sources/CLVMTools/IR/IRReader.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
import Foundation
import BigInt
import CLVM

typealias Token = (String, Int)
typealias Stream = IndexingIterator<[Token]>

enum SyntaxError: Error {
case unexpectedEndOfStream
case missingClosingParenthesis
case unterminatedString(Int, String)
case illegalDotExpression(Int)
case invalidHex(Int, String)
case badIRFormat(SExp)
}

/// This also deals with comments.
func consume_whitespace(s: String, offset: Int) -> Int {
var offset = offset
while true {
while offset < s.count && s[s.index(s.startIndex, offsetBy: offset)].isWhitespace {
offset += 1
}
if offset >= s.count || s[s.index(s.startIndex, offsetBy: offset)] != ";" {
break
}
while offset < s.count && !Set(["\n", "\r"]).contains(s[s.index(s.startIndex, offsetBy: offset)]) {
offset += 1
}
}
return offset
}

func consume_until_whitespace(s: String, offset: Int) -> Token {
let start = offset
var offset = offset
while offset < s.count && !s[s.index(s.startIndex, offsetBy: offset)].isWhitespace && s[s.index(s.startIndex, offsetBy: offset)] != ")" {
offset += 1
}
return (String(s[s.index(s.startIndex, offsetBy: start)..<s.index(s.startIndex, offsetBy: offset)]), offset)
}

func next_cons_token(stream: inout Stream) throws -> Token {
if let token = stream.next() {
return token
}
throw SyntaxError.missingClosingParenthesis
}

func tokenize_cons(token: String, offset: Int, stream: inout Stream) throws -> CLVMObject {
if token == ")" {
return CLVMObject(v: .sexp(try ir_new(type: .NULL, val: .int(0), offset: offset)))
}

let initial_offset = offset

let first_sexp = try tokenize_sexp(token: token, offset: offset, stream: &stream)

var (token, offset) = try next_cons_token(stream: &stream)
let rest_sexp: CLVMObject
if token == "." {
let dot_offset = offset
// grab the last item
(token, offset) = try next_cons_token(stream: &stream)
rest_sexp = try tokenize_sexp(token: token, offset: offset, stream: &stream)
(token, offset) = try next_cons_token(stream: &stream)
if token != ")" {
throw SyntaxError.illegalDotExpression(dot_offset)
}
}
else {
rest_sexp = try tokenize_cons(token: token, offset: offset, stream: &stream)
}
return CLVMObject(v: .sexp(
try ir_cons(
first: SExp(obj: first_sexp),
rest: SExp(obj: rest_sexp),
offset: initial_offset
)
))
}

func tokenize_int(token: String, offset: Int) throws -> CLVMObject? {
do {
// hack to avoid assert in bigint
if token == "-" {
return nil
}
guard let int = BigInt(token) else {
throw ValueError("Invalid Int")
}
return CLVMObject(v: .sexp(try ir_new(type: .INT, val: .int(int), offset: offset)))
}
catch is ValueError {
//pass
}
return nil
}

func tokenize_hex(token: String, offset: Int) throws -> CLVMObject? {
if token.prefix(2).uppercased() == "0X" {
do {
var token = String(token.suffix(token.count - 2))
if token.count % 2 == 1 {
token = "0" + token
}
return CLVMObject(v: .sexp(try ir_new(type: .HEX, val: .bytes(Data(hex: token)), offset: offset)))
}
catch {
throw SyntaxError.invalidHex(offset, token)
}
}
return nil
}

func tokenize_quotes(token: String, offset: Int) throws -> CLVMObject? {
if token.count < 2 {
return nil
}
let c = token.first
if !Set(["\'", "\\", "\""]).contains(c) {
return nil
}

if token.last != c {
throw SyntaxError.unterminatedString(offset, token)
}

let q_type: IRType = c == "'" ? .SINGLE_QUOTE : .DOUBLE_QUOTE

return CLVMObject(v: .tuple((.tuple((.int(BigInt(q_type.rawValue)), .int(BigInt(offset)))), .bytes(token.suffix(token.count-1).data(using: .utf8)!))))
}

func tokenize_symbol(token: String, offset: Int) -> CLVMObject? {
return CLVMObject(v: .tuple((
.tuple((
.int(BigInt(IRType.SYMBOL.rawValue)),
.int(BigInt(offset))
)),
.bytes(token.data(using: .utf8)!)
)))
}

func tokenize_sexp(token: String, offset: Int, stream: inout Stream) throws -> CLVMObject {
if token == "(" {
let (token, offset) = try next_cons_token(stream: &stream)
return try tokenize_cons(token: token, offset: offset, stream: &stream)
}

for f in [
tokenize_int,
tokenize_hex,
tokenize_quotes,
tokenize_symbol,
] {
if let r = try f(token, offset) {
return r
}
}

throw ValueError("Invalid sexp")
}

func token_stream(s: String) throws -> Stream {
// Python implements this with yield.. we can do something similar with AsyncThrowingStream
// but then everything becomes async.
var tokens: [Token] = []
var offset = 0
while offset < s.count {
offset = consume_whitespace(s: s, offset: offset)
if offset >= s.count {
break
}
let c = s[s.index(s.startIndex, offsetBy: offset)]
if ["(", ".", ")"].contains(c) {
tokens.append((String(c), offset))
offset += 1
continue
}
if ["\\", "\"", "'"].contains(c) {
let start = offset
let initial_c = s[s.index(s.startIndex, offsetBy: start)]
offset += 1
while offset < s.count && s[s.index(s.startIndex, offsetBy: offset)] != initial_c {
offset += 1
}
if offset < s.count {
tokens.append((String(s[s.index(s.startIndex, offsetBy: start)..<s.index(s.startIndex, offsetBy: offset + 1)]), start))
offset += 1
continue
}
else {
throw SyntaxError.unterminatedString(start, String(s.suffix(s.count - start)))
}
}
let (token, end_offset) = consume_until_whitespace(s: s, offset: offset)
tokens.append((token, offset))
offset = end_offset
}
return tokens.makeIterator()
}

func read_ir(
s: String,
to_sexp: @escaping (CastableType) throws -> SExp = SExp.to
) throws -> SExp {
var stream = try token_stream(s: s)
guard let (token, offset) = stream.next() else {
throw SyntaxError.unexpectedEndOfStream
}
return try to_sexp(.object(try tokenize_sexp(
token: token,
offset: offset,
stream: &stream
)))
}


#warning("hack")

extension Data {
public init(hex: String) {
self.init(Array<UInt8>(hex: hex))
}
}

// Data(hex:)
// From https://github.com/krzyzanowskim/CryptoSwift/ (MIT)
extension Array where Element == UInt8 {
public init(hex: String) {
self.init()
reserveCapacity(hex.unicodeScalars.lazy.underestimatedCount)
var buffer: UInt8?
var skip = hex.hasPrefix("0x") ? 2 : 0
for char in hex.unicodeScalars.lazy {
guard skip == 0 else {
skip -= 1
continue
}
guard char.value >= 48 && char.value <= 102 else {
removeAll()
return
}
let v: UInt8
let c: UInt8 = UInt8(char.value)
switch c {
case let c where c <= 57:
v = c - 48
case let c where c >= 65 && c <= 70:
v = c - 55
case let c where c >= 97:
v = c - 87
default:
removeAll()
return
}
if let b = buffer {
append(b << 4 | v)
buffer = nil
} else {
buffer = v
}
}
if let b = buffer {
append(b)
}
}
}
18 changes: 18 additions & 0 deletions Sources/CLVMTools/IR/IRType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Foundation

/// Associated values are `Int.from_bytes(b, .big)` where b is the utf8 encoding of the type's symbol.
public enum IRType: Int {
case CONS = 1129270867
case NULL = 1314212940
case INT = 4804180
case HEX = 4736344
case QUOTES = 20820
case DOUBLE_QUOTE = 4477268
case SINGLE_QUOTE = 5460308
case SYMBOL = 5462349
case OPERATOR = 20304
case CODE = 1129268293
case NODE = 1313817669
}

let CONS_TYPES: Set<IRType> = Set([.CONS])
Loading

0 comments on commit 16dc9e1

Please sign in to comment.