Contents

dankogai/swift-bf

swift-bf

SYNOPSIS

Interpreter

import BF
var bf = try BF("+[,<>.]-")            // echo
print(bf.run(input: "Hello, Swift!"))  // Hello, Swift!

BF.init throws a BF.ParseError if the brackets do not balance:

do {
    _ = try BF("+[")
} catch {
    print(error)  // unmatched [ at offset 1
}

You can also drive the machine one command at a time. step() returns false once the program halts, and pc, sp, data and obuf are all readable as it goes:

var bf = try BF("++")
while bf.step() {}
print(bf.data[0])  // 2

The ! extension

A ! ends the program, and everything after it is the program's input, so a source can carry its own data:

var bf = try BF("+[,<>.]-!Hello, Swift!")
print(bf.run())  // Hello, Swift!

The text after ! is available as embeddedInput, and is what run() feeds , by default. Passing input: explicitly overrides it for that run without discarding it:

var bf = try BF("+[,.]!embedded")
print(bf.run(input: "explicit"))  // explicit
print(bf.run())                   // embedded

Only the first ! splits the source, so the input may contain more of them -- which is why "+[,<>.]-!Hello, Swift!" echoes the trailing ! too.

Compiler

import BF
print(BF.compile("+[,<>.]-"))

prints

#if canImport(Darwin)
import Darwin
#else
import Glibc
#endif
var data = [UInt8](repeating: 0, count: 65536)
var sp = 0
data[sp] &+= 1
while data[sp] != 0 {
    data[sp] = UInt8(truncatingIfNeeded: max(0, getchar()))
    sp = (sp + data.count - 1) % data.count
    sp = (sp + 1) % data.count
    putchar(Int32(data[sp]))
}
data[sp] &-= 1

which compiles and runs standalone:

swift run bf | tail -n +2 > echo.swift
swiftc -o echo echo.swift
echo 'Hello, Swift!' | ./echo

A source using ! compiles to a self-contained program that needs no stdin -- the input is baked in and , reads from it:

print(BF.compile("+[,<>.]-!Hello, Swift!"))

emits the same program as above, but with

var input = Array("Hello, Swift!".utf8)[...]

after the preamble, and , compiled to

data[sp] = input.popFirst() ?? 0

Semantics

  • The tape is 65536 cells of UInt8. Cells wrap around: - on a zero cell

yields 255.

  • The tape is circular. < on cell 0 moves to cell 65535, and > on the

last cell moves back to cell 0.

  • ! ends the program; everything after the first one is input, not code.

Brackets in the input section are data and need not balance.

  • Any other character outside the input section is a comment.
  • On end of input, , halts the interpreter, whereas compiled code follows

the other common convention and stores 0.

Usage

Swift Package Manager

Add it to the dependencies of your Package.swift:

let package = Package(
  // ...
  dependencies: [
    .package(url: "https://github.com/dankogai/swift-bf.git", from: "0.0.2")
  ],
  targets: [
    .target(name: "YourTarget", dependencies: [
      .product(name: "BF", package: "swift-bf")
    ])
  ]
)

Build and test locally

swift build
swift test
swift run bf

Requirements

Swift 6.0 or later.

Package Metadata

Repository: dankogai/swift-bf

Default branch: main

README: README.md