Skip to content

Instantly share code, notes, and snippets.

@yamasushi
Last active December 16, 2023 04:39
Show Gist options
  • Save yamasushi/0eb6dc0e023dcb2fa01102e973868903 to your computer and use it in GitHub Desktop.
Save yamasushi/0eb6dc0e023dcb2fa01102e973868903 to your computer and use it in GitHub Desktop.
desk calculator
# calc
# https://gist.github.com/yamasushi/0eb6dc0e023dcb2fa01102e973868903
class Calc
def initialize(lex,var,fn,op)
@lex=lex
@var=var
@fn =fn
@op =op
end
# restx -> <op> termx {restx_1.inh = restx.inh <op> termx.syn }
# restx_1 {restx.syn = restx_1.syn}
# | ε {rest.syn = rest.inh}
def restx(syms, termx, inh)
#puts "restx: syms=#{syms},inh=#{inh} lex.peek=#{@lex.peek}"
_restx_ = lambda{ |inh, lex , op|
#puts "_restx_: inh=#{inh} lex.peek=#{lex.peek}"
return inh if lex.empty? # ε
hd= lex.get
#puts "check #{hd and hd[0]==:op and ind=syms.index(hd[1])}"
if hd and hd[0]==:op and ind=syms.index(hd[1])
#puts "----------lex.peek =#{lex.peek} -------"
op_sym=syms[ind]
#puts "_restx_: op_sym=#{op_sym} , inh=#{inh} , lex.peek=#{lex.peek}"
syn=termx::()
#puts "_restx_: syn=#{syn}"
syn=_restx_::( op::(op_sym,inh,syn) ,lex,op)
return syn
else # ε
#puts "-----------ε-------------"
lex.unget(hd)
return inh
end
}
_restx_::( inh , @lex , @op)
end
# expr -> term {rest.inh = term.syn}
# rest {expr.syn = rest.syn}
def expr
#puts "expr: inh=#{inh}, lex.peek=#{@lex.peek}"
syn = self.term1
syn = self.rest1 syn
return syn
end #class
# rest1 -> + { term1.inh = rest1.inh}
# term1 {rest1_1.inh = rest1.inh + term1.syn }
# rest1_1 {rest1.syn = rest1_1.syn}
# ...
# | ε {rest1.syn = rest.inh}
def rest1(inh)
self.restx( [ :+ , :- ] , lambda{||self.term1} , inh)
end
# term1 -> {term2.inh = term1.inh}
# term2 {rest2.inh = term2.syn}
# rest2 {term.syn = rest2.syn}
def term1
#puts "term1: inh=#{inh}, lex.peek=#{@lex.peek}"
syn = self.term2
syn = self.rest2 syn
return syn
end
# rest2 -> * {term2.inh=rest2.inh}
# term2 {rest2_1.inh = rest2.inh * term2.syn }
# rest2_1 {rest2.syn = rest2_1.syn}
# ...
# | ε {rest.syn = rest.inh}
def rest2(inh)
self.restx( [ :* , :/ , :% ] , lambda{||self.term2} , inh)
end
# term2 -> {term3.inh=term2.inh}
# term3 {rest3.inh = term3.syn}
# rest3 {term2.syn = rest3.syn}
def term2
#puts "term2: inh=#{inh}, lex.peek=#{@lex.peek}"
syn = self.term3
syn = self.rest3 syn
return syn
end
def rest3(inh)
self.restx( [ :^ ] , lambda{||self.term3} , inh)
end
def term3
self.factor
end
def match_term(t)
#puts "match-term t=#{t} lex.peek=#{@lex.peek}"
raise "syntax error t=#{t} lex.peek=#{@lex.peek}" if @lex.empty?
[email protected]
if not Lexer::token_eq(hd,t)
raise "syntax error t=#{t} lex.peek=#{@lex.peek}"
end
return nil
end
# factor -> <integer> {factor.syn = <integer>.syn }
# | ( expr ) {factor.syn = expr.syn}
# | -factor1 {factor.syn = -factor1.syn}
# | <function> ( expr ) { <function> ( expr.syn) }
def factor
#puts "factor inh=#{inh} lex.peek=#{@lex.peek}"
raise "factor:error lex.peek=#{@lex.peek}" if @lex.empty?
hd = @lex.get
raise "factor:error hd=#{hd} lex.peek=#{@lex.peek}" if hd.nil?
if hd==[:op , :-] # negation
return @fn::( :- , self.factor )
elsif hd==[:paren , :"("] # left parenthesis
syn = self.expr
self.match_term([:paren, :")"])
return syn
elsif hd[0]==:id # constant
return @var::( hd[1] )
elsif hd[0]==:id_func # function
self.match_term([:paren, :"("])
syn = self.expr
self.match_term([:paren, :")"])
return @fn::( hd[1],syn )
elsif hd[0]==:number #number
return hd[1]
else
raise "factor error: hd=#{hd}"
end
end
end #class
=begin
=end
# test calc
# https://gist.github.com/yamasushi/0eb6dc0e023dcb2fa01102e973868903
load "./lex.rb"
load "./calc.rb"
class CalcMain
@@op_symmap={
:+ => lambda{ |x,y| x + y } ,
:- => lambda{ |x,y| x - y } ,
:/ => lambda{ |x,y| x / y } ,
:* => lambda{ |x,y| x * y } ,
:% => lambda{ |x,y| x % y } ,
:^ => lambda{ |x,y| x ** y } }
@@fn_symmap={
:- => lambda{ |x| -x} ,
:sin => lambda{ |d| Math::sin ( d * Math::PI / 180 ) } ,
:cos => lambda{ |d| Math::cos ( d * Math::PI / 180 ) } ,
:tan => lambda{ |d| Math::tan ( d * Math::PI / 180 ) } ,
:ln => lambda{ |x| Math::log (x) } ,
:log => lambda{ |x| Math::log10(x) } }
@@vars={
:pi => Math::PI ,
:e => Math::E }
def self.op(op,x,y) @@op_symmap[op].(x,y) end
def self.fn(fn,x) @@fn_symmap[fn].(x) end
def self.var(v) @@vars[v] end
def self.test_calc(str)
Calc.new(
Lexer.new(StringIO.new(str,"r")) ,
lambda{|vsym| self.var(vsym) } ,
lambda{|fnsym,x| self.fn(fnsym,x)} ,
lambda{|opsym,x,y| self.op(opsym,x,y)} ).expr
end
end
class Sexp
def self.test_calc(str)
Calc.new(
Lexer.new(StringIO.new(str,"r")) ,
lambda{|vsym| vsym } ,
lambda{|fnsym,x| [fnsym,x]} ,
lambda{|opsym,x,y| [opsym,x,y]} ).expr
end
end
# lexical analysis
# https://gist.github.com/yamasushi/0eb6dc0e023dcb2fa01102e973868903
require "stringio"
def lexer_test(str)
Lexer.new(StringIO.new(str,"r"))
end
class Lexer
#--------------------------------------
@@base=10
@@re_digit = /[0-9]/
def self.digit2int(d)
d.to_i
end
#--------------------------------------
@@re_id_head = /[a-z]/
@@re_id_body = /[a-z0-9]/
@@re_parentheses = /[()]/
@@re_op = /[+\-*\/%\\^]/
#--------------------------------------
# token def.
def self.token_number(v)
return [:number , v]
end
def self.token_op(v)
return [:op , v]
end
def self.token_id(v)
return [:id , v]
end
def self.token_id_func(v)
return [:id_func , v]
end
def self.token_paren(v)
return [:paren , v]
end
def self.token_eq(t,s)
raise "error oken_eq t=#{t} s=#{s}" if t.nil? or s.nil?
t[0]==s[0] and t[1]==s[1]
end
#--------------------------------------
# ctor.
def initialize(input_io)
@inp =input_io
@stack=[]
@fiber=Lexer.make_fiber(@inp)
end
#--------------------------------------
# operations
def get
if not @stack.empty?
@stack.pop
else
# stack is empty
if self.fiber_is_empty?
# fiber is empty too
return nil
else
@fiber.resume
end
end
end
def unget(t)
@stack.push(t)
return nil
end
def peek
return nil if empty?
t=self.get
self.unget(t)
t
end
def empty?
@stack.empty? and fiber_is_empty?
end
def get_all
val=[]
while t=self.get
val.push(t)
end
val
end
def fiber_is_empty?
@fiber.nil? or (not @fiber.alive?)
end
#--------------------------------------
# fiber
def self.make_fiber(inp)
Fiber.new do
Lexer.lexer(inp) {|t| Fiber.yield(t)}
end
end
#--------------------------------------
# main
def self.lexer(inp,&blk)
c=skip_blank(inp)
return nil if c.nil?
case c
when /\s/
lexer(inp,&blk)
when @@re_digit
i=pos_int(inp , digit2int(c),1)[0]
#
c=inp.getc
if c=~/\./
# integer with decimal point
c=inp.getc
if @@re_digit=~ c
yield( token_number( i+decimal(inp,c) ) )
else
inp.ungetc(c)
yield( token_number( i ) )
end
else
# integer
inp.ungetc(c)
yield( token_number( i) )
end
lexer(inp,&blk)
when /\./
# decimal point
c=inp.getc
if @@re_digit=~ c
yield( token_number( decimal(inp,c) ) )
lexer(inp,&blk)
else
raise "error c=#{c}"
end
when @@re_id_head
# head of identifier
sym = id(inp,c)
c = skip_blank(inp) #next char
token=if c.nil? or c!="("
token_id(sym)
else
# id() ... function call
token_id_func(sym)
end
yield( token )
inp.ungetc(c)
lexer(inp,&blk)
when @@re_op
yield( token_op(c.to_sym) )
lexer(inp,&blk)
when @@re_parentheses
yield( token_paren(c.to_sym) )
lexer(inp,&blk)
else
raise "lexer error: c=#{c}"
end
return nil
end
def self.skip_blank(inp)
c=inp.getc
return nil if c.nil?
if c =~ /\s/
return skip_blank(inp)
else
return c
end
end
# positive intger
def self.pos_int(inp , acc,k)
#puts "k=#{k},acc=#{acc}"
c = inp.getc
return [acc,k] if c.nil?
case c
when @@re_digit
pos_int(inp , acc * @@base + digit2int(c) , k+1)
else
inp.ungetc(c)
return [acc,k]
end
end
#decimal
def self.decimal(inp , hd)
i,k=pos_int(inp , digit2int(hd),1)
return i.to_f/(@@base**k)
end
# identifier
def self.id(inp,acc)
raise "id error" if acc.nil?
c=inp.getc
return acc.to_sym if c.nil?
if c =~ @@re_id_body
id(inp,acc+c)
else
inp.ungetc(c)
return acc.to_sym
end
end
end # class
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment