Last active
December 16, 2023 04:39
-
-
Save yamasushi/0eb6dc0e023dcb2fa01102e973868903 to your computer and use it in GitHub Desktop.
desk calculator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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