Project

jaina

0.0
No release in over 3 years
Low commit activity in last 3 years
Simple programming language builder inspired by interpreter pattern. You can build your own languages with custom operands and operators for any project purposes.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 0.8
>= 0
>= 0
~> 3.8
~> 0.16
 Project Readme

Jaina ยท Gem Version Build Status Coverage Status

Simple programming language builder inspired by interpreter pattern. You can build your own languages with custom operands and operators for any project purposes.

Installation

gem 'jaina'
$ bundle install
# --- or ---
$ gem install 'jaina'
require 'jaina'

Usage

  • Registered operators
  • Register your own operator
  • Register your own operand
  • Context API
  • Parse your code (build AST)
  • Evaluate your code
  • Custom operator/operand arguments
  • List registered operands and operators
  • Full example

Registered operators

  • AND
  • OR
  • NOT
  • (, ) (grouping operators)

Register your own operator

# step 1: define new operator
class But < Jaina::NonTerminalExpr
  token 'BUT' # use it in your program :)
  associativity_direction :left # associativity (left or right)
  acts_as_binary_term # binar or unary
  precedence_level 4 # for example: AND > OR, NOT > AND, and etc...
end

# step 2: regsiter your operator
Jaina.register_expression(But)

Register your own operand

# step 1: define new operand
class A < Jaina::TerminalExpr
  token 'A'

  # NOTE: context is a custom data holder that passed from expression to expression
  def evaluate(context)
    # your custom evaluation code
  end
end

# step 2: regsiter your operand
Jaina.register_expression(A)

# step X: redefine existing operand (with the same token)
class NewA < Jaina::TerminalExpr
  token 'A'
end
Jaina.redefine_expression(NewA)

Context API

class A < Jaina::TerminalExpr
  # ... some code

  def evaluate(context)
    ... your code ...
    # ... context ???
    ... your code ...
  end
end

# NOTE: context api

context.keys # => []
context.set(:a, 1) # => 1
context.get(:a) # => 1
context.keys # => [:a]
context.get(:b) # => Jaina::Parser::AST::Contex::UndefinedContextKeyError

Parse your code (build AST)

# NOTE: without arguments
Jaina.parse('A AND B AND (C OR D) OR A AND (C OR E)')
# => #<Jaina::Parser::AST:0x00007fd6f424a2e8>

# NOTE: with arguments
Jaina.parse('A[1,2] AND B[3,4]')
# => #<Jaina::Parser::AST:0x00007fd6f424a2e9>

Evaluate your code

ast = Jaina.parse('A AND B[5,test] AND (C OR D) OR A AND (C OR E)')
ast.evaluate

# --- or ---
Jaina.evaluate('A AND B[5,test] AND (C OR D) OR A AND (C OR E)')

# --- you can set initial context of your program ---
Jaina.evaluate('A AND B[5,test]', login: 'admin', logged_in: true)

Custom operator/operand arguments

# NOTE: use []
Jaina.parse('A[1,true] AND B[false,"false"]')

# NOTE:
#   all your arguments will be typecasted to
#   the concrete type inferred from the argument literal

Jaina.parse('A[1,true,false,"false"]') # 1, true, false "false"

# NOTE: access to the argument list
class A < Jaina::TerminalExpr
  token 'A'

  def evaluate(context)
    # A[1,true,false,"false"]

    arguments[0] # => 1
    arguments[1] # => true
    arguments[2] # => false
    arguments[3] # => "false"
  end
end

List and fetch registered operands and operators

A = Class.new(Jaina::TerminalExpr) { token 'A' }
B = Class.new(Jaina::TerminalExpr) { token 'B' }
C = Class.new(Jaina::TerminalExpr) { token 'C' }

Jaina.register_expression(A)
Jaina.register_expression(B)
Jaina.register_expression(C)

Jaina.expressions
# => ["AND", "OR", "NOT", "(", ")", "A", "B", "C"]

Jaina.fetch_expression("AND") # => Jaina::Parser::Expression::Unit::And
Jaina.fetch_expression("A") # => A

Jaina.fetch_expression("KEK")
# => raises Jaina::Parser::Expression::Registry::UnregisteredExpressionError

Full example

# step 1: create new operand
class AddNumber < Jaina::TerminalExpr
  token 'ADD'

  def evaluate(context)
    context.set(:current_value, context.get(:current_value) + 10)
  end
end

# step 2: create another new operand
class CheckNumber < Jaina::TerminalExpr
  token 'CHECK'

  def evaluate(context)
    context.get(:current_value) < 0
  end
end

# step 4: and another new :)
class InitState < Jaina::TerminalExpr
  token 'INIT'

  def evaluate(context)
    initial_value = arguments[0] || 0

    context.set(:current_value, initial_value)
  end
end

# step 5: register new oeprands
Jaina.register_expression(AddNumber)
Jaina.register_expression(CheckNumber)
Jaina.register_expression(InitState)

# step 6: run your program

# NOTE: with initial context
Jaina.evaluate('CHECK AND ADD', current_value: -1) # => 9
Jaina.evaluate('CHECK AND ADD', current_value: 2) # => false

# NOTE: without initial context
Jaina.evaluate('INIT AND ADD') # => 10
Jaina.evaluate('INIT AND (CHECK OR ADD)') # => 10

# NOTE: with arguments
Jaina.evaluate('INIT[100] AND ADD') => # 112

Contributing

  • Fork it ( https://github.com/0exp/jaina/fork )
  • Create your feature branch (git checkout -b feature/my-new-feature)
  • Commit your changes (git commit -am 'Add some feature')
  • Push to the branch (git push origin feature/my-new-feature)
  • Create new Pull Request

License

Released under MIT License.

Authors

Rustam Ibragimov