Git Product home page Git Product logo

math-compiler's Introduction

Go Report Card license

Table of Contents

math-compiler

This project contains the simplest possible compiler, which converts mathematical operations into assembly language, allowing all the speed in your sums!

Because this is a simple project it provides only a small number of primitives:

  • + - Plus
  • - - Minus
  • * - Multiply
  • / - Divide
  • ^ - Raise to a power
  • % - Modulus
  • ! - Factorial
  • abs
  • sin
  • cos
  • tan
  • sqrt
  • Stack operations:
    • swap - Swap the top-two items on the stack
    • dup - Duplicate the topmost stack-entry.
  • Built-in constants:
    • e
    • pi

Despite this toy-functionality there is a lot going on, and we support:

  • Full RPN input
  • Floating-point numbers (i.e. one-third multipled by nine is 3)
    • 1 3 / 9 *
  • Negative numbers work as you'd expect.

Some errors will be caught at run-time, as the generated code has support for:

  • Detecting, and preventing, division by zero.
  • Detecting insufficient arguments being present upon the stack.
    • For example this program is invalid 3 +, because the addition operator requires two operands. (i.e. 3 4 +)

Installation

There are two ways to install this project from source, which depend on the version of the go version you're using.

If you just need the binaries you can find them upon the project release page.

Source Installation go <= 1.11

If you're using go before 1.11 then the following command should fetch/update the project and install it upon your system:

 $ go get -u github.com/skx/math-compiler

Source installation go >= 1.12

If you're using a more recent version of go (which is highly recommended), you need to clone to a directory which is not present upon your GOPATH:

git clone https://github.com/skx/math-compiler
cd math-compiler
go install

Quick Overview

The intention of this project is mostly to say "I wrote a compiler", because I've already experimented with a language, an embedded evaluation engine, and implemented a BASIC interpreter. The things learned from those projects were pretty useful, even if the actual results were not so obviously useful in themselves.

Because there are no shortages of toy-languages, and there is a lot of complexity in writing another for no real gain, I decided to just focus upon a simple core:

  • Allowing "maths stuff" to be "compiled".

In theory this would allow me to compile things like this:

2 + ( 4 * 54 )

However I even simplified that, via the use of a "Reverse Polish" notation, so if you want to run that example you'd enter the expression as:

4 54 * 2 +

About Our Output

The output of math-compiler will typically be an assembly-language file, which then needs to be compiled before it may be executed.

Given our previous example of 2 + ( 4 * 54) we can compile & execute that program like so:

$ math-compiler '4 54 * 2+' > sample.s
$ gcc -static -o sample ./sample.s
$ ./sample
Result 218

There you see:

  • math-compiler was invoked, and the output written to the file sample.s.
  • gcc was used to assemble sample.s into the binary sample.
  • The actual binary was then executed, which showed the result of the calculation.

If you prefer you can also let the compiler do the heavy-lifting, and generate an executable for you directly. Simply add -compile, and execute the generated a.out binary:

$ math-compiler -compile=true '2 8 ^'
$ ./a.out
Result 256

Or to compile and execute directly:

$ math-compiler -run '3 45 * 9 + 12 /'
Result 12

Test Cases

The codebase itself contains some simple test-cases, however these are not comprehensive as a large part of our operation is merely to populate a simple template-file, and it is hard to test that.

To execute the integrated tests use the standard go approach:

$ go test [-race] ./...

In addition to the internal test cases there are also some functional tests contained in test.sh - these perform some calculations and verify they produce the correct result.

frodo ~/go/src/github.com/skx/math-compiler $ ./test.sh
...
Expected output found for '2 0 ^' [0]
Expected output found for '2 1 ^' [2]
Expected output found for '2 2 ^' [4]
Expected output found for '2 3 ^' [8]
Expected output found for '2 4 ^' [16]
Expected output found for '2 5 ^' [32]
...
Expected output found for '2 30 ^' [1073741824]
...

Debugging the generated programs

If you run the compiler with the -debug flag a breakpoint will be generated immediately at the start of the program. You can use that breakpoint to easily debug the generated binary via gdb.

For example you might generate a program "2 3 + 4 /" like so:

$ math-compiler -compile -debug '2 3 + 4 /'

Now you can launch that binary under gdb, and run it:

$ gdb ./a.out
(gdb) run
..
Program received signal SIGTRAP, Trace/breakpoint trap.
0x00000000006b20cd in main ()

Dissassemble the code via disassemble, and step over instructions one at a time via stepi. If your program is long you might see a lot of output from the disassemble step:

(gdb) disassemble
Dump of assembler code for function main:
   0x00000000006b20cb:	push   %rbp
   0x00000000006b20cc:	int3
=> 0x00000000006b20cd:	fldl   0x6b20b3
   0x00000000006b20d4:	fstpl  0x6b2090
   0x00000000006b20db:	mov    0x6b2090,%rax
   0x00000000006b20e3:	push   %rax
   0x00000000006b20e4:	fldl   0x6b20bb
   0x00000000006b20eb:	fstpl  0x6b2090
   0x00000000006b20f2:	mov    0x6b2090,%rax
   0x00000000006b20fa:	push   %rax
   ...
   ...

You can set a breakpoint at a line in the future, and continue running till you hit it, with something like this:

 (gdb) break *0x00000000006b20fa
 (gdb) cont

Once there inspect the registers with commands like these two:

 (gdb) print $rax
 (gdb) info registers

My favourite is info registers float, which shows you the floating-point values as well as the raw values:

 (gdb) info registers float
 st0            0.140652076786443369638	(raw 0x3ffc90071917a6263000)
 st1            0	(raw 0x00000000000000000000)
 st2            0	(raw 0x00000000000000000000)
 ...
 ...

Further documentation can be found in the gdb manual, which is worth reading if you've an interest in compilers, debuggers, and decompilers.

Possible Expansion?

The obvious thing to improve in this compiler is to add support for more operations. At the moment support for the most obvious/common operations is present, but perhaps more functions could be added.

See Also

If you enjoyed this repository, then you might also enjoy my compiler for the Brainfuck language. The compiler there compiles brainfuck programs to x86-64 assembly-language:

Github Setup

This repository is configured to run tests upon every commit, and when pull-requests are created/updated. The testing is carried out via .github/run-tests.sh which is used by the github-action-tester action.

Releases are automated in a similar fashion via .github/build, and the github-action-publish-binaries action.

Steve

math-compiler's People

Contributors

skx avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

math-compiler's Issues

Support floating-point operations.

I spent a few hours over the weekend re-acquainting myself with floating-point operations, and it wouldn't be hard to switch from integers to using them.

Currently we always keep the starting number inf rax, then make sure that all operations use rax as the source. We'll do something similar with the FPU instructions, keep [acc] as the accumulator, and use that as the source for all issues.

The biggest different I guess, is that we'll use the data-region to store our constants. Since you can't run:

 mov eax, 3.13

Instead you need something like this:

    ; this example sets `acc = a + b`
    section .data
       a:      dq      3.0   
       b:      dq      4.0

    section .bss
       acc:      resq    1    ; our temporary / result / accumulator.


    section .text

    global  main

    main:                           
       fld    qword [a]       
       fadd qword [b]       
       fstp  qword [acc]  

     ..

It's not horrid, but it means that we need to generate a list of constants, which can happen from any operation.

It's probably worth templating add, div, mul, & other sections. Rather than using one constant template.

As long as all operations work on the acc temporary value, and their own operand it'll be simple enough though :)

Convert to being 100% RPN-based.

Currently the input we parse is like a reverse-polish stack-based calculator, but it is not reall.

We should make it such that we are, allowing input such as this:

 1 2 3 4 5 6 7 8 9 10+++++++++
  (-> 55 )

This isn't a significant change, but could be improved by code-folding. The biggest difficulty will be that we want to split the template we use for generating the program into distinct steps.

Add `dup`

This would be useful, for duplicating the stack-top.

For example:

  ' 3 dup *  '
   -> 9

Move code out of main.go

I added some tests for the lexer, and ideally should ensure there is some test-coverage for the main script.

To do that I'll need to create a simple Compiler class, and move most of the code out of main.go.

This is probably a pretty mechanical transformation..

Implement factorial

For example:

  • 1! => 1
  • 2! => 2
  • 3! => 6
  • 4! => 4 * 3 * 2 * 1 = 24
  • 5! => 5 * 4 * 3 * 2 * 1 = 120

Not sure it will be hugely useful, but it's a simple thing to add.

Simplify the internal API

The main.go driver needs to know about tokens, internal form, etc.

Export a single function in the compiler. package which does the compilation, and hides those details.

That makes usage simpler.

Detect overflow ..

We can't hold all numbers in our result-values, as we only have 64-bits.

This presents itself most obviously in the case of the factorial operation:

  $ ./math-compiler -run '20!'
  Result 2.4329e+18

   $ ./math-compiler -run '21!'
   Result -4.24929e+18

The first output there is valid, and correct. The second is negative, as a result of overflow.

We should be able to catch that at run-time, and error rather than showing the bogus-result.

Handle the case of insufficient arguments.

All our operators pop items from the stack, do something, then store the result back there.

We should abort, cleanly, if there are insufficient arguments present upon the stack.

This would allow the following program to abort cleanly:

    $ math-compiler -run '3 +'
    Error launching a.out: signal: segmentation fault (core dumped)

We generate code to push (numbers) items upon the stack. I guess we allocate a register, or address, to keep a running count of stack-entries. Increment when we add, and decrement when we remove.

Should be a simple change. Also a good opportunity to cleanup the termination behaviour.

Support modulus

For example 6 % 5 => 1. Though of course we'd expect 6 5 % as input to our compiler.

Convert github actions.

Github actions used to be defined via HCL, now they use YAML files.

Convert and improve as appropriate.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.