GithubHelp home page GithubHelp logo

stack-calc's Introduction

stack-calc

Build Status Coverage Status GPL 2+

simple stack calculation engine without any deeper meaning

This is just a coding exercise.

language

stack-calc implements a stack (actually a stack of stacks ;-) and can do simple calculations with the values on the stack.

tokens

A command is split into tokens separated by whitespace. Any token that is not an operation or a group delimiter will be put on the stack. These tokens should only be numeric values or everything will break.

operations

(notation: T1 is the value at the top of the current stack, T2 is the second-to-top value and so on)

  • + will add T1 and T2 and put the result on the stack

  • - will take T1, subtract T2 from it and put the result on the stack

  • * will multiply T1 and T2 and put the result on the stack

  • / will divide T1 by T2 and put the result on the stack

  • !+ (!-, !*, !/) will repeatedly execute + (-, *, /) unless there is only one value left in the current stack

  • d will drop T1 elements from the beginning of the current stack

  • di (drop indirect) will drop T1 elements from the beginning of the group at T2

  • seq will generate an integer sequence from 1 to T1

groups

  • ( … ) creates a normal (sub-)stack. It is evaluated while it is parsed and after parsing the whole stack is replaced by its top value. This stack type is useful to set boundaries for a repeated command (!+ etc.).

  • [ … ] creates a list. It is evaluated while it is parsed, but after that the list is retained. An operation will be applied to every list item and create a result list in place. Operating on two lists will create something like a cross product consisting of nested lists.

  • { … } creates a block. In a block, operations are not evaluated during parsing - only groups are. Subgroups will be evaluated normally. This is used to create the body of named functions.

The implicit initial main stack is a normal stack (( … )): It is evaluated and its top value is returned.

named functions

defun will define a function named T1 (this is a rare instance where a token can (and should - don't redefine numbers to functions!) be a string) that expects T2 arguments. The function's code body is given via a block a T3.

When later a token is encountered that is the name of a previously defined function, the following happens:

  • as many arguments as defined are taken off the current stack
  • a temporary stack is created
  • the arguments are added to the temporary stack
  • the function's code body is added to the temporary stack
  • the temporary stack is evaluated
  • the top element of the evaluated temporary stack is put onto the current stack

examples

  • 1 2 + adds 1 to 2
  • 2 5 / divides 5 by 2
  • 5 seq !+ adds up all integers from 1 to 5
  • ( 9 seq !+ ) ( 15 seq !+ ) - adds up all integers from 10 to 15
  • 15 seq 9 d !+ does the same. Both variants are very inefficient for big numbers.
  • 6 seq !* calculates !6 (factorial)
  • [ 10 20 30 ] 2 * doubles every value in the list
  • { seq !* } 1 fac defun [ 4 fac 6 fac ] defines a factorial function named fac that takes one argument. Note that the definition all by itself evaluates to nothing and leaves an empty stack. Two prevent a stack underflow on execution, a list containing two invocations of fac has also been added.

REPL

Running stack-calc as a command enters REPL mode. Every line of input is processed and the result (the top value of the stack after processing) is printed.

Modulino

You can require stack-calc; and then call the execute() method with your command as a parameter. The command will be processed and the result returned.

This is currently not very useful as stack-calc pollutes the main:: namespace. This feature only there to make proper testing possible.

installation

To install all dependencies via cpanm run make installdeps. If you use another installation method, run make -s showdeps to show what is needed.

The stack-calc script currently is self-sufficient when all dependencies are installed, just put it where you want it to be.

copyright

Copyright (C) 2017 Christian Garbs [email protected]
Licensed under GNU GPL v2 or later.

stack-calc's People

Contributors

mmitch avatar

Watchers

 avatar  avatar

stack-calc's Issues

ensure matching group endings

Currently you can do unmatched groups like this [ 1 2 } ( 3 4 ] and try to guess what will happen.
Add proper error handling for unbalanced brackets.

use arbitrary precision

look for some Perl libraries to do this

actually we're quite good already, !150 can be calculated without any problems

add map, reduce, foreach and flatten

This should help with the specification problems in #4 and solve some of the redundancies in #3
It will also remove the need for the special commands !+, !- etc.

add variable definition

A variable definition is just a special form of a function definition:

3 foo defvar could transformed to { 3 } 0 foo defun and executed.

Variables and functions would then share a common namespace.

duplicate code

  • sub-stack creation and finalizing looks very similar for all groups
  • recursion for list and block handling looks very similar for inner_seq() and inner_op2
    ** but seems tricky to generalize

list handling: whole list or contents?

Currently seq applied to a list will create sublists:
[ 1 2 3 ] seq -> [ [ 1 ] [ 1 2 ] [ 1 2 3 ] ]
This is inconsistent because normally seq will just push multiple values onto the current stack without wrapping them in a group. But providing a flat list is not very useful: [ 1 1 2 1 2 3 ]. Also changing the number of list elements is unusual. Perhaps this can be seen as a case of cross-product and everything is OK.

What about applying a function to a list?

  • Should the list be bound as a parameter inside the function? This might be useful.
  • Should the function be applied to every parameter of the list? This might be useful, too.

Same thing with drop: Currently there is plain drop (d) which is treating lists in any way: It will just drop a list. To drop elements from inside a list, there is di (drop indirect). But there is currently no way to drop elements from inside multiple lists (dii? how many indirections do we need?), think of transforming [ [ 1 2 ] [ 3 4 ] [ 5 6 ] ] into [ [ 2 ] [ 4 ] [ 6 ] ].

Perhaps we need explicit map and flatMap operations that tie in together with other operations and remove the explicit list handling currently hardcoded to every (well, at least some) operation? This might also help with deduping the list handling parts in the code (see #3).

argument checking

Let every operation define argument count and types like LL for two lists or T for a single token.
This will allow for better error messages.

Variant:
Provide a lookup table per argument combination:

  • LL -> two lists, generate cross product
  • LT, TL -> apply token to every list member
  • TT -> run operation once

provide verbose mode

Add a special operation that can enable/disable verbose mode. This setting should persist over multiple calculations. Could be a commandline argument to the REPL script.

Sketch:
[2] 1 2 3 4 5 <= !*

  • [2] length of stack_stack
  • 1 2 3 4 5 current stack
  • !* current token

Should be printed on every process_token() invocation

split into classes

It's about time to introduce classes and objects: there are multiple instances of if (ref $token eq 'HASH') and if ($token->{TYPE} eq 'LIST') revealing that tokens act differently based on their type.

Possible classes:

  • REPL
  • Stack containing any of
    ** Token
    ** Group
    ** Command
    ** FunctionCall

add local functions / variables

Create something like closures: Every stack instance has it's own funcs hash that is cloned from the parent stack upon creation. Defining functions (and variables) thus only works locally.

add named parameters to functions

Instead of putting the arguments of a function at the bottom of the temporary function stack on execution, the arguments could be provided as named parameters (eg. T1, T2…).

This should be very easy once #10 and #11 have been implemented.

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.