Bakgrunn
Et av mine favorittprogrammer er IPython, et alternativt REPL for Python med snacks som autocomplete og historikk. Jeg bruker IPython mest som kalkulator i kommandolinja, men opplever stadig at det knekker når jeg bytter mellom ulike Python-versjoner på prosjekter.
I stedet for å finne en mer fornuftig måte å håndtere disse versjonene på, bestemte jeg meg heller for å lage min egen kalkulator – og slik ble Abacus født. Hvor vanskelig kunne det egentlig være?
Hvordan skal dette bli
Jeg ønsket å evaluere matematiske uttrykk, lagre resultater i variabler, og bruke variabler videre i nye uttrykk. Det gikk fort opp for meg at jeg i praksis var i ferd med å implementere et lite programmeringsspråk. Og hva er et programmeringsspråk uten funksjoner?
De siste årene har jeg latt meg begeistre av Rust. En av de mest elegante funksjonene i Rust er pattern matching, som er tydelig inspirert av Haskell. I Haskell kan man til og med bruke pattern matching direkte i funksjonssignaturer, noe som eliminerer behovet for mange tradisjonelle kontrollstrukturer. Dette ønsket jeg å ta med inn i Abacus.
Rust
let x = 1;
match x {
1 => println!("one"),
2 => println!("two"),
3 => println!("three"),
_ => println!("anything"),
}
Haskell
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = n * factorial (n-1)Kjernefunksjonalitet
Jeg endte opp med et minimalt, men målrettet sett med funksjonalitet:
- Evaluering av uttrykk
- Variabler
- Funksjoner med pattern matching i signaturer
Fra et tidligere fag hadde jeg erfaring med å lage en Pascal-kompilator, så kompilatorteknikk var ikke helt fremmed. Først må man bryte kildekoden ned i tokens i en prosess kalt lexing. Deretter bygges et syntakstre via parsing, før treet til slutt evalueres.
Input leses tegn for tegn og gjøres om til tokens. Deretter bygger jeg syntakstreet med Pratt-parsing, som gir en fleksibel måte å håndtere operatorpresedens på. Til slutt evalueres treet rekursivt, og resultatet spyttes ut.
Det var viktig for meg at REPL-et skulle være fargerikt og ergonomisk å bruke. Jeg landet på rustyline for håndtering av input og kommandohistorikk, miette for ryddige og fargerike feilmeldinger, og clap til å parse kommandolinjeargumenter.
Abacus kommer neppe til å revolusjonere verden, men det har vært et gøy og lærerikt prosjekt. Koden ligger på GitHub under MIT-lisens, og du kan installere Abacus via cargo. Jeg kommer til å fortsette utviklingen, så gi gjerne en stjerne om du vil, eller meld inn issues hvis du finner bugs eller har feature-ønsker.
Ting jeg ønsker å legge til i fremtiden
- Range-typer: 0..10, 0..=100
- Wildcard-pattern _
- Flere integer-literal-formater (binary, hex, octal)
- Strings og chars
- Et minimalt standardbibliotek
Eksempler
Hvordan installere Abacus
cargo install abacusREPL-eksempel
[ABACUS - Calculator REPL]
Type expressions or 'quit' to exit
[0x00]: 2 + 2
[0x00]: 4
[0x01]: factorial(n) = n * factorial(n - 1)
[0x02]: factorial(0) = 1
[0x03]: factorial(5)
[0x03]: 120
[0x04]: