ceremonyclient/bedlam/circuit/parser.go
Cassandra Heart e51992f3e8
OT
2025-03-23 21:11:16 -05:00

512 lines
10 KiB
Go

//
// Copyright (c) 2019-2023 Markku Rossi
//
// All rights reserved.
//
package circuit
import (
"bufio"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"os"
"regexp"
"strconv"
"strings"
"source.quilibrium.com/quilibrium/monorepo/bedlam/types"
)
var reParts = regexp.MustCompilePOSIX("[[:space:]]+")
// Seen describes whether wire has been seen.
type Seen []bool
// Get gets the wire seen flag.
func (s Seen) Get(index Wire) (bool, error) {
if index >= Wire(len(s)) {
return false, fmt.Errorf("invalid wire %d [0...%d[", index, len(s))
}
return s[index], nil
}
// Set marks the wire seen.
func (s Seen) Set(index Wire) error {
if index >= Wire(len(s)) {
return fmt.Errorf("invalid wire %d [0...%d[", index, len(s))
}
s[index] = true
return nil
}
// IsFilename tests if the argument file is a potential circuit
// filename.
func IsFilename(file string) bool {
return strings.HasSuffix(file, ".circ") ||
strings.HasSuffix(file, ".bristol") ||
strings.HasSuffix(file, ".qclc")
}
// Parse parses the circuit file.
func Parse(file string) (*Circuit, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
defer f.Close()
if strings.HasSuffix(file, ".circ") || strings.HasSuffix(file, ".bristol") {
return ParseBristol(f)
} else if strings.HasSuffix(file, ".qclc") {
return ParseQCLC(f)
}
return nil, fmt.Errorf("unsupported circuit format")
}
// ParseQCLC parses an QCL circuit file.
func ParseQCLC(in io.Reader) (*Circuit, error) {
r := bufio.NewReader(in)
var header struct {
Magic uint32
NumGates uint32
NumWires uint32
NumInputs uint32
NumOutputs uint32
}
if err := binary.Read(r, bo, &header); err != nil {
return nil, err
}
var inputs, outputs IO
var inputWires, outputWires int
wiresSeen := make(Seen, header.NumWires)
for i := 0; i < int(header.NumInputs); i++ {
arg, err := parseIOArg(r)
if err != nil {
return nil, err
}
inputs = append(inputs, arg)
inputWires += int(arg.Type.Bits)
}
for i := 0; i < int(header.NumOutputs); i++ {
out, err := parseIOArg(r)
if err != nil {
return nil, err
}
outputs = append(outputs, out)
outputWires += int(out.Type.Bits)
}
// Mark input wires seen.
for i := 0; i < inputWires; i++ {
if err := wiresSeen.Set(Wire(i)); err != nil {
return nil, err
}
}
gates := make([]Gate, header.NumGates)
var stats Stats
var gate int
for gate = 0; ; gate++ {
op, err := r.ReadByte()
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
switch Operation(op) {
case XOR, XNOR, AND, OR:
var bin struct {
Input0 uint32
Input1 uint32
Output uint32
}
if err := binary.Read(r, bo, &bin); err != nil {
return nil, err
}
seen, err := wiresSeen.Get(Wire(bin.Input0))
if err != nil {
return nil, err
}
if !seen {
return nil, fmt.Errorf("input %d of gate %d not set",
bin.Input0, gate)
}
seen, err = wiresSeen.Get(Wire(bin.Input1))
if err != nil {
return nil, err
}
if !seen {
return nil, fmt.Errorf("input %d of gate %d not set",
bin.Input1, gate)
}
if err := wiresSeen.Set(Wire(bin.Output)); err != nil {
return nil, err
}
gates[gate] = Gate{
Input0: Wire(bin.Input0),
Input1: Wire(bin.Input1),
Output: Wire(bin.Output),
Op: Operation(op),
}
case INV:
var unary struct {
Input0 uint32
Output uint32
}
if err := binary.Read(r, bo, &unary); err != nil {
return nil, err
}
seen, err := wiresSeen.Get(Wire(unary.Input0))
if err != nil {
return nil, err
}
if !seen {
return nil, fmt.Errorf("input %d of gate %d not set",
unary.Input0, gate)
}
if err := wiresSeen.Set(Wire(unary.Output)); err != nil {
return nil, err
}
gates[gate] = Gate{
Input0: Wire(unary.Input0),
Output: Wire(unary.Output),
Op: Operation(op),
}
default:
return nil, fmt.Errorf("unsupported gate type %s", Operation(op))
}
stats[Operation(op)]++
}
if uint32(gate) != header.NumGates {
return nil, fmt.Errorf("not enough gates: got %d, expected %d",
gate, header.NumGates)
}
// Check that all wires are seen.
for i := 0; i < len(wiresSeen); i++ {
if !wiresSeen[i] {
return nil, fmt.Errorf("wire %d not assigned", i)
}
}
return &Circuit{
NumGates: int(header.NumGates),
NumWires: int(header.NumWires),
Inputs: inputs,
Outputs: outputs,
Gates: gates,
Stats: stats,
}, nil
}
func parseIOArg(r *bufio.Reader) (arg IOArg, err error) {
name, err := parseString(r)
if err != nil {
return arg, err
}
t, err := parseString(r)
if err != nil {
return arg, err
}
var ui32 uint32
if err := binary.Read(r, bo, &ui32); err != nil {
return arg, err
}
arg.Name = name
arg.Type, err = types.Parse(t)
if err != nil {
return arg, err
}
arg.Type.Bits = types.Size(ui32)
// Compound
if err := binary.Read(r, bo, &ui32); err != nil {
return arg, err
}
for i := 0; i < int(ui32); i++ {
c, err := parseIOArg(r)
if err != nil {
return arg, err
}
arg.Compound = append(arg.Compound, c)
}
return
}
func parseString(r *bufio.Reader) (string, error) {
var ui32 uint32
if err := binary.Read(r, bo, &ui32); err != nil {
return "", err
}
if ui32 == 0 {
return "", nil
}
buf := make([]byte, ui32)
_, err := r.Read(buf)
if err != nil {
return "", err
}
return string(buf), nil
}
// ParseBristol parses a Briston circuit file.
func ParseBristol(in io.Reader) (*Circuit, error) {
r := bufio.NewReader(in)
// NumGates NumWires
line, err := readLine(r)
if err != nil {
return nil, err
}
if len(line) != 2 {
return nil, fmt.Errorf("invalid 1st line: '%s'", line)
}
numGates, err := strconv.Atoi(line[0])
if err != nil {
return nil, err
}
if numGates < 0 || numGates > math.MaxInt32 {
return nil, fmt.Errorf("invalid numGates: %d", numGates)
}
numWires, err := strconv.Atoi(line[1])
if err != nil {
return nil, err
}
if numWires < 0 || numWires > math.MaxInt32 {
return nil, fmt.Errorf("invalid numWires: %d", numWires)
}
wiresSeen := make(Seen, numWires)
// Inputs
line, err = readLine(r)
if err != nil {
return nil, err
}
niv, err := strconv.Atoi(line[0])
if err != nil {
return nil, err
}
if 1+niv != len(line) {
return nil, fmt.Errorf("invalid inputs line: niv=%d, len=%d",
niv, len(line))
}
var inputs IO
var inputWires int64
for i := 1; i < len(line); i++ {
bits, err := strconv.ParseInt(line[i], 10, 32)
if err != nil {
return nil, fmt.Errorf("invalid input bits: %s", err)
}
if bits < 0 {
return nil, fmt.Errorf("invalid input bits: %d", bits)
}
inputs = append(inputs, IOArg{
Name: fmt.Sprintf("NI%d", i),
Type: types.Info{
Type: types.TUint,
IsConcrete: true,
Bits: types.Size(bits),
},
})
inputWires += bits
}
if inputWires == 0 {
return nil, fmt.Errorf("no inputs defined")
}
// Mark input wires seen.
for i := int64(0); i < inputWires; i++ {
if err := wiresSeen.Set(Wire(i)); err != nil {
return nil, err
}
}
// Outputs
line, err = readLine(r)
if err != nil {
return nil, err
}
nov, err := strconv.Atoi(line[0])
if err != nil {
return nil, err
}
if 1+nov != len(line) {
return nil, errors.New("invalid outputs line")
}
var outputs IO
for i := 1; i < len(line); i++ {
bits, err := strconv.ParseInt(line[i], 10, 32)
if err != nil {
return nil, fmt.Errorf("invalid output bits: %s", err)
}
if bits < 0 {
return nil, fmt.Errorf("invalid output bits: %d", bits)
}
outputs = append(outputs, IOArg{
Name: fmt.Sprintf("NO%d", i),
Type: types.Info{
Type: types.TUint,
IsConcrete: true,
Bits: types.Size(bits),
},
})
}
gates := make([]Gate, numGates)
var stats Stats
var gate int
for gate = 0; ; gate++ {
line, err = readLine(r)
if err != nil {
if err == io.EOF {
break
}
return nil, err
}
if gate >= numGates {
return nil, errors.New("too many gates")
}
if len(line) < 3 {
return nil, fmt.Errorf("invalid gate: %v", line)
}
n1, err := strconv.Atoi(line[0])
if err != nil {
return nil, err
}
if n1 < 0 {
return nil, fmt.Errorf("invalid n1: %v", n1)
}
n2, err := strconv.Atoi(line[1])
if err != nil {
return nil, err
}
if n2 < 0 {
return nil, fmt.Errorf("invalid n2: %v", n2)
}
if 2+n1+n2+1 != len(line) {
return nil, fmt.Errorf("invalid gate: %v", line)
}
var inputs []Wire
for i := 0; i < n1; i++ {
v, err := strconv.ParseUint(line[2+i], 10, 32)
if err != nil {
return nil, err
}
seen, err := wiresSeen.Get(Wire(v))
if err != nil {
return nil, err
}
if !seen {
return nil, fmt.Errorf("input %d of gate %d not set", v, gate)
}
inputs = append(inputs, Wire(v))
}
var outputs []Wire
for i := 0; i < n2; i++ {
v, err := strconv.ParseUint(line[2+n1+i], 10, 32)
if err != nil {
return nil, err
}
err = wiresSeen.Set(Wire(v))
if err != nil {
return nil, err
}
outputs = append(outputs, Wire(v))
}
var op Operation
var numInputs int
switch line[len(line)-1] {
case "XOR":
op = XOR
numInputs = 2
case "XNOR":
op = XNOR
numInputs = 2
case "AND":
op = AND
numInputs = 2
case "OR":
op = OR
numInputs = 2
case "INV":
op = INV
numInputs = 1
default:
return nil, fmt.Errorf("invalid operation '%s'", line[len(line)-1])
}
if len(inputs) != numInputs {
return nil, fmt.Errorf("invalid number of inputs %d for %s",
len(inputs), op)
}
if len(outputs) != 1 {
return nil, fmt.Errorf("invalid number of outputs %d for %s",
len(outputs), op)
}
var input1 Wire
if len(inputs) > 1 {
input1 = inputs[1]
}
gates[gate] = Gate{
Input0: inputs[0],
Input1: input1,
Output: outputs[0],
Op: op,
}
stats[op]++
}
if gate != numGates {
return nil, fmt.Errorf("not enough gates: got %d, expected %d",
gate, numGates)
}
// Check that all wires are seen.
for i := 0; i < len(wiresSeen); i++ {
if !wiresSeen[i] {
return nil, fmt.Errorf("wire %d not assigned", i)
}
}
return &Circuit{
NumGates: numGates,
NumWires: numWires,
Inputs: inputs,
Outputs: outputs,
Gates: gates,
Stats: stats,
}, nil
}
func readLine(r *bufio.Reader) ([]string, error) {
for {
line, err := r.ReadString('\n')
if err != nil {
return nil, err
}
line = strings.TrimSpace(line)
if len(line) == 0 {
continue
}
parts := reParts.Split(line, -1)
if len(parts) > 0 {
return parts, nil
}
}
}