M.Hiroi's Home Page
http://www.geocities.jp/m_hiroi/

Julia Language Programming

簡単なプログラム (3)

[ Home | Light | Julia ]

●式の計算 (再帰降下法)

再帰降下法で式 (四則演算とカッコ) を計算するプログラムです。アルゴリズムの詳細は拙作のページ Scheme Programming 電卓プログラムの作成 をお読みください。

#
# calc.jl : 式の計算 (単純な再帰降下法)
#
#           Copyright (C) 2016 Makoto Hiroi
#

# 大域変数
# ch    : 入力文字 (Char)
# token : トークン (Symbol)
# value : 値 (Float64)

# 記号の先読み
function nextch()
    global ch
    ch = read(STDIN, Char)
end

# 記号の読み込み
getch() = ch

# 整数値の読み込み
function get_fixnum(buff)
    while isdigit(getch())
        push!(buff, getch())
        nextch()
    end
end

# 数値を求める
function get_number()
    buff::Array{Char, 1} = []
    get_fixnum(buff)
    if getch() == '.'
        push!(buff, getch())
        nextch()
        get_fixnum(buff)
    end
    if getch() == 'e' || getch() == 'E'
        push!(buff, getch())
        nextch()
        if getch() == '+' || getch() == '-'
            push!(buff, getch())
            nextch()
        end
        get_fixnum(buff)
    end
    parse(Float64, join(buff))
end

# トークンの切り分け
function get_token()
    global token, value
    # 空白文字の読み飛ばし
    while isspace(getch()); nextch(); end
    if isdigit(getch())
        token = :NUMBER
        value = get_number()
    elseif getch() == '+'
        token = :ADD
        nextch()
    elseif getch() == '-'
        token = :SUB
        nextch()
    elseif getch() == '*'
        token = :MUL
        nextch()
    elseif getch() == '/'
        token = :DIV
        nextch()
    elseif getch() == '('
        token = :LPAR
        nextch()
    elseif getch() == ')'
        token = :RPAR
        nextch()
    elseif getch() == ';'
        token = :SEMIC
        nextch()
    else
        token = :OTHERS
    end
end

# 構文解析

# 式
function expression()
    global token
    val = term()
    while true
        if token === :ADD
            get_token()
            val += term()
        elseif token === :SUB
            get_token()
            val -= term()
        else
            break
        end
    end
    val
end

# 項
function term()
    global token
    val = factor()
    while true
        if token === :MUL
            get_token()
            val *= factor()
        elseif token === :DIV
            get_token()
            val /= factor()
        else
            break
        end
    end
    val
end

# この処理を factor() に埋め込むと動作しない (たぶん Julia のバグ)
function factor_sub()
    global token
    get_token()
    v = expression()
    if token === :RPAR
        get_token()
    else
        error("')' expected")
    end
    v
end

# 因子
function factor()
    global token, value
    if token === :LPAR
        factor_sub()
    elseif token === :NUMBER
        get_token()
        value
    elseif token === :ADD
        get_token()
        factor()
    elseif token === :SUB
        get_token()
        -factor()
    else
        error("unexpected token")
    end
end

# トップレベル
function toplevel()
    global token
    local val = expression()
    if token === :SEMIC
        @printf "=> %.14g\nCalc> " val
        flush(STDOUT)
    else
        error("invalid token")
    end
end

function calc()
    print("Calc> ")
    flush(STDOUT)
    nextch()
    while true
        try
            get_token()
            toplevel()
        catch e
            print("ERROR: ")
            showerror(STDOUT, e)
            println("")
            # 入力のクリア
            while getch() != '\n'; nextch(); end
            print("Calc> ")
            flush(STDOUT)
        end
    end
end

# 実行
calc()
Calc> 1 + 2 + 3;
=> 6
Calc> (1 + 2) * (3 - 4);
=> -3
Calc> 1.2345678 * 1.1111111;
=> 1.3717419862826
Calc> 1 / 7;
=> 0.14285714285714
Calc> -1;
=> -1
Calc> -10 * -10;
=> 100
Calc> 1 + * 2;
ERROR: unexpected token
Calc> (1 + 2;
ERROR: ')' expected
Calc>  <--- Ctrl-C で終了

●式の計算 (構文木の構築)

再帰降下法で構文木を構築して式 (四則演算とカッコ) を計算するプログラムです。アルゴリズムの詳細は拙作のページ お気楽 SML/NJ プログラミング入門 電卓プログラムの作成 をお読みください。

#
# calc1.jl : 式の計算 (再帰降下法で構文木を構築)
#
#            Copyright (C) 2016 Makoto Hiroi
#

# 大域変数
# ch    : 文字 (Char)
# token : トークン (Symbol)
# value : 値 (Float64)

# 記号の先読み
function nextch()
    global ch
    ch = read(STDIN, Char)
end

# 記号の読み込み
getch() = ch

# 整数値の読み込み
function get_fixnum(buff)
    while isdigit(getch())
        push!(buff, getch())
        nextch()
    end
end

# 数値を求める
function get_number()
    buff::Array{Char, 1} = []
    get_fixnum(buff)
    if getch() == '.'
        push!(buff, getch())
        nextch()
        get_fixnum(buff)
    end
    if getch() == 'e' || getch() == 'E'
        push!(buff, getch())
        nextch()
        if getch() == '+' || getch() == '-'
            push!(buff, getch())
            nextch()
        end
        get_fixnum(buff)
    end
    parse(Float64, join(buff))
end

# トークンの切り分け
function get_token()
    global token, value
    # 空白文字の読み飛ばし
    while isspace(getch()); nextch(); end
    if isdigit(getch())
        token = :NUMBER
        value = get_number()
    elseif getch() == '+'
        token = :ADD
        nextch()
    elseif getch() == '-'
        token = :SUB
        nextch()
    elseif getch() == '*'
        token = :MUL
        nextch()
    elseif getch() == '/'
        token = :DIV
        nextch()
    elseif getch() == '('
        token = :LPAR
        nextch()
    elseif getch() == ')'
        token = :RPAR
        nextch()
    elseif getch() == ';'
        token = :SEMIC
        nextch()
    else
        token = :OTHERS
    end
end

# 構文木
# 二項演算子
type Op2
    op
    left
    right
end

# 単項演算子
type Op1
    op
    right
end

# 構文解析

# 式
function expression()
    global token
    expr = term()
    while true
        if token === :ADD
            get_token()
            expr = Op2(:ADD2, expr, term())
        elseif token === :SUB
            get_token()
            expr = Op2(:SUB2, expr, term())
        else
            break
        end
    end
    expr
end

# 項
function term()
    global token
    expr = factor()
    while true
        if token === :MUL
            get_token()
            expr = Op2(:MUL2, expr, factor())
        elseif token === :DIV
            get_token()
            expr = Op2(:DIV2, expr, factor())
        else
            break
        end
    end
    expr
end

#
function factor_sub()
    global token
    get_token()
    expr = expression()
    if token === :RPAR
        get_token()
    else
        error("')' expected")
    end
    expr
end

# 因子
function factor()
    global token, value
    if token === :LPAR
        factor_sub()
    elseif token === :NUMBER
        get_token()
        value
    elseif token === :ADD
        get_token()
        Op1(:ADD1, factor())
    elseif token === :SUB
        get_token()
        Op1(:SUB1, factor())
    else
        error("unexpected token")
    end
end

# 構文木の評価
function eval_expr(expr)
    if typeof(expr) == Float64
        expr
    elseif typeof(expr) == Op2
        l_val = eval_expr(expr.left)
        r_val = eval_expr(expr.right)
        if expr.op === :ADD2
            l_val + r_val
        elseif expr.op === :SUB2
            l_val - r_val
        elseif expr.op === :MUL2
            l_val * r_val
        elseif expr.op === :DIV2
            l_val / r_val
        else
            error("unknown Op2")
        end
    elseif typeof(expr) == Op1
        val = eval_expr(expr.right)
        if expr.op === :ADD1
            val
        elseif expr.op === :SUB1
            -val
        else
            error("unknown Op1")
        end
    else
        error("broken expression")
    end
end

# トップレベル
function toplevel()
    global token
    expr = expression()
    if token === :SEMIC
        @printf "=> %.14g\nCalc> " eval_expr(expr)
        flush(STDOUT)
    else
        error("invalid token")
    end
end

function calc()
    print("Calc> ")
    flush(STDOUT)
    nextch()
    while true
        try
            get_token()
            toplevel()
        catch e
            print("ERROR: ")
            showerror(STDOUT, e)
            println("")
            # 入力のクリア
            while getch() != '\n'; nextch(); end
            print("Calc> ")
            flush(STDOUT)
        end
    end
end

# 実行
calc()

●式の計算 (組み込み関数と大域変数の追加)

式を計算するプログラムに組み込み関数と大域変数の機能を追加したものです。アルゴリズムの詳細は拙作のページ お気楽 SML/NJ プログラミング入門 電卓プログラムの作成 (2) をお読みください。

#
# calc3.jl : 式の計算 (組み込み関数と大域変数の追加)
#
#            Copyright (C) 2016 Makoto Hiroi
#

# 大域変数
# ch    : 文字 (Char)
# token : トークン (Symbol)
# value : 値 (Float64)

# 記号の先読み
function nextch()
    global ch
    ch = read(STDIN, Char)
end

# 記号の読み込み
getch() = ch

# 整数値の読み込み
function get_fixnum(buff)
    while isdigit(getch())
        push!(buff, getch())
        nextch()
    end
end

# 数値を求める
function get_number()
    buff::Array{Char, 1} = []
    get_fixnum(buff)
    if getch() == '.'
        push!(buff, getch())
        nextch()
        get_fixnum(buff)
    end
    if getch() == 'e' || getch() == 'E'
        push!(buff, getch())
        nextch()
        if getch() == '+' || getch() == '-'
            push!(buff, getch())
            nextch()
        end
        get_fixnum(buff)
    end
    parse(Float64, join(buff))
end

# 識別子 (identifier) を求める
function get_ident()
    buff::Array{Char, 1} = []
    while isalnum(getch())
        push!(buff, getch())
        nextch()
    end
    Symbol(join(buff))
end

# トークンの切り分け
function get_token()
    global token, value
    # 空白文字の読み飛ばし
    while isspace(getch()); nextch(); end
    if isdigit(getch())
        token = :NUMBER
        value = get_number()
    elseif isalpha(getch())
        token = :IDENT
        value = get_ident()
    elseif getch() == '+'
        token = :ADD
        nextch()
    elseif getch() == '-'
        token = :SUB
        nextch()
    elseif getch() == '*'
        token = :MUL
        nextch()
    elseif getch() == '/'
        token = :DIV
        nextch()
    elseif getch() == '('
        token = :LPAR
        nextch()
    elseif getch() == ')'
        token = :RPAR
        nextch()
    elseif getch() == ','
        token = :COMMA
        nextch()
    elseif getch() == '='
        token = :ASGN
        nextch()
    elseif getch() == ';'
        token = :SEMIC
        nextch()
    else
        token = :OTHERS
    end
end

# 構文木
# 二項演算子
type Op2
    op
    left
    right
end

# 単項演算子
type Op1
    op
    right
end

# 関数
type Func
    fn
    args
end

# 組み込み関数
func_table = Dict{Symbol, Function}(
    :sqrt => sqrt,
    :sin => sin,
    :cos => cos,
    :tan => tan,
    :asin => asin,
    :acos => acos,
    :atan => atan,
    :atan2 => atan2,
    :exp => exp,
    :pow => ^,
    :ln => log,
    :log => x -> log(10, x),
    :sinh => sinh,
    :cosh => cosh,
    :tanh => tanh
)

# 大域変数
global_variable = Dict{Symbol, Float64}()

# 構文解析

# 式
function expression()
    global token
    expr = expr1()
    if token === :ASGN
        if typeof(expr) == Symbol
            get_token()
            expr = Op2(:ASGN2, expr, expression())
        else
            error("invalid assign form")
        end
    end
    expr
end

function expr1()
    global token
    expr = term()
    while true
        if token === :ADD
            get_token()
            expr = Op2(:ADD2, expr, term())
        elseif token === :SUB
            get_token()
            expr = Op2(:SUB2, expr, term())
        else
            break
        end
    end
    expr
end

# 項
function term()
    global token
    expr = factor()
    while true
        if token === :MUL
            get_token()
            expr = Op2(:MUL2, expr, factor())
        elseif token === :DIV
            get_token()
            expr = Op2(:DIV2, expr, factor())
        else
            break
        end
    end
    expr
end

#
function factor_sub()
    global token
    get_token()
    expr = expression()
    if token === :RPAR
        get_token()
    else
        error("')' expected")
    end
    expr
end

# 引数の取得
function get_arguments()
    args = []
    if token !== :LPAR
        error("'(' expected")
    end
    get_token()
    while true
        push!(args, expression())
        if token !== :COMMA; break; end
        get_token()
    end
    if token !== :RPAR
        error("')' expected")
    end
    get_token()
    args
end

# 因子
function factor()
    global token, value
    if token === :LPAR
        factor_sub()
    elseif token === :NUMBER
        get_token()
        value
    elseif token === :ADD
        get_token()
        Op1(:ADD1, factor())
    elseif token === :SUB
        get_token()
        Op1(:SUB1, factor())
    elseif token === :IDENT
        get_token()
        if haskey(func_table, value)
            Func(func_table[value], get_arguments())
        else 
            value
        end
    else
        error("unexpected token")
    end
end

# 式の評価
function eval_expr(expr)
    if typeof(expr) == Float64
        expr
    elseif typeof(expr) == Symbol
        if haskey(global_variable, expr)
            global_variable[expr]
        else
            error("undefined variable")
        end
    elseif typeof(expr) == Op2
        if expr.op == :ASGN2
            global_variable[expr.left] = eval_expr(expr.right)
        else
            l_val = eval_expr(expr.left)
            r_val = eval_expr(expr.right)
            if expr.op === :ADD2
                l_val + r_val
            elseif expr.op === :SUB2
                l_val - r_val
            elseif expr.op === :MUL2
                l_val * r_val
            elseif expr.op === :DIV2
                l_val / r_val
            else
                error("unknown Op2")
            end
        end
    elseif typeof(expr) == Op1
        val = eval_expr(expr.right)
        if expr.op === :ADD1
            val
        elseif expr.op === :SUB1
            -val
        else
            error("unknown Op1")
        end
    elseif typeof(expr) == Func
        args = map(x -> eval_expr(x), expr.args)
        expr.fn(args...)
    else
        error("broken expression")
    end
end

# トップレベル
function toplevel()
    global token
    expr = expression()
    if token === :SEMIC
        @printf "=> %.14g\nCalc> " eval_expr(expr)
        flush(STDOUT)
    else
        error("invalid token")
    end
end

function calc()
    print("Calc> ")
    flush(STDOUT)
    nextch()
    while true
        try
            get_token()
            toplevel()
        catch e
            print("ERROR: ")
            showerror(STDOUT, e)
            println("")
            # 入力のクリア
            while getch() != '\n'; nextch(); end
            print("Calc> ")
            flush(STDOUT)
        end
    end
end

# 実行
calc()
Calc> a = 10;
=> 10
Calc> a;
=> 10
Calc> a * 10;
=> 100
Calc> (b = 20) * 10;
=> 200
Calc> b;
=> 20
Calc> x = y = z = 0;
=> 0
Calc> x;
=> 0
Calc> y;
=> 0
Calc> z;
=> 0
Calc> p = p + 1;
ERROR: undefined variable
Calc> q = 1;
=> 1
Calc> q = q + 1;
=> 2
Calc> q;
=> 2
Calc> sqrt(2);
=> 1.4142135623731
Calc> pow(2, 32);
=> 4294967296
Calc> pow(2, 32) - 1;
=> 4294967295
Calc> pi = asin(0.5) * 6;
=> 3.1415926535898
Calc> sin(0);
=> 0
Calc> sin(pi);
=> -3.2162452993533e-16
Calc> sin(pi/2);
=> 1

●最小の Lisp

小さな小さな Scheme ライクの Lisp インタプリタです。最小の Lisp については、拙作のページ Scheme Programming Scheme で作る micro Scheme をお読みください。

#
# mscm.jl : Micro Scheme インタプリタ
#
#           Copyright (C) 2016 Makoto Hiroi
#

# 大域変数
# ch : 文字 (Char)

# 記号の先読み
function nextch()
    global ch
    ch = read(STDIN, Char)
end

# 記号の読み込み
getch() = ch

# 整数値の読み込み
function get_fixnum(buff)
    while isdigit(getch())
        push!(buff, getch())
        nextch()
    end
end

# 数値を求める
function get_number()
    buff::Array{Char, 1} = []
    flag = false
    push!(buff, getch())
    nextch()
    get_fixnum(buff)
    if getch() == '.'
        flag = true
        push!(buff, getch())
        nextch()
        get_fixnum(buff)
    end
    if getch() == 'e' || getch() == 'E'
        flag = true
        push!(buff, getch())
        nextch()
        if getch() == '+' || getch() == '-'
            push!(buff, getch())
            nextch()
        end
        get_fixnum(buff)
    end
    if length(buff) == 1 && !isdigit(buff[1])
        symbol(buff[1])
    elseif flag
        parse(Float64, join(buff))
    else
        parse(Int128, join(buff))
    end
end

# シンボルに含めてよい記号
code_list = "!&*+-/:<=>?@^_~"

# 識別子 (identifier) を求める
function get_ident()
    buff::Array{Char, 1} = []
    while isalnum(getch()) || search(code_list, getch()) > 0
        push!(buff, getch())
        nextch()
    end
    Symbol(join(buff))
end

# セル (空リストは :nil)
type Cons
    car
    cdr
end

# クロージャ
type Closure
    para
    body
    env
end

# コンスセルか?
consp(xs) = typeof(xs) == Cons

# 空リストか?
null(xs) = xs === :nil

# 数か?
numberp(x) = isa(x, Number)

# シンボルか?
symbolp(x) = typeof(x) == Symbol

#
# read
#

# 空白文字の読み飛ばし
skipspace() = while isspace(getch()); nextch(); end

# リストの読み込み
function read_list()
    skipspace()
    if getch() == ')'
        nextch()
        :nil
    elseif getch() == '.'
        nextch()
        x = read_s()
        skipspace()
        if getch() != ')'
            error("invalid dot list")
        end
        nextch()
        x
    else
        Cons(read_s(), read_list())
    end
end

function read_s()
    skipspace()
    c = getch()
    if isdigit(c) || c == '+' || c == '-'
        get_number()
    elseif isalpha(c) || search(code_list, c) > 0
        get_ident()
    elseif getch() == '\''
        nextch()
        Cons(:quote, Cons(read_s(), :nil))
    elseif getch() == '('
        nextch()
        read_list()
    else
        error("invalid token")
    end
end

#
# print
#
function print_s(xs)
    if consp(xs)
        print("(")
        while consp(xs)
            if typeof(xs.car) == Cons
                print_s(xs.car)
            else
                print(xs.car)
            end
            if !null(xs.cdr); print(" "); end
            xs = xs.cdr
        end
        if !null(xs)
            print(". "); print(xs)
        end
        print(")")
    else
        print(xs)
    end
end

#
# 環境
#

# 大域変数
oblist = Dict{Symbol, Any}(
    # 真偽値 (Common Lisp ライク)
    :t => :t,
    :nil => :nil,

    :car => xs -> xs.car,
    :cdr => xs -> xs.cdr,
    :cons => (x, y) -> Cons(x, y),
    Symbol("eq?") => (x, y) -> x === y ? :t : :nil,
    Symbol("pair?") => xs -> consp(xs) ? :t : :nil,

    :+ => (args...) -> +(args...),
    :* => (args...) -> *(args...),
    :- => (n, args...) -> if length(args) == 0
                              -n
                          else
                              for x in args; n -= x; end
                              n
                          end,
    :/ => (n, args...) -> if length(args) == 0
                              1 / n
                          else
                              for x in args; n /= x; end
                              n
                          end,
    Symbol("=") => (x, y) -> x == y ? :t : :nil,
    :<  => (x, y) -> x <  y ? :t : :nil,
    :<= => (x, y) -> x <= y ? :t : :nil,
    :>  => (x, y) -> x >  y ? :t : :nil,
    :>= => (x, y) -> x >= y ? :t : :nil
)

# 連想リストの探索
function assoc(xs, x)
    while !null(xs)
        if xs.car.car == x; return xs.car; end
        xs = xs.cdr
    end
    :nil
end

# 参照
function lookup(var, env)
    global oblist
    # 局所変数の探索
    xs = assoc(env, var)
    if !null(xs)
        xs.cdr
    elseif haskey(oblist, var)
        # 大域変数
        oblist[var]
    else
        error("undefind variable")
    end
end

#=
# 更新
function update(var, x, env)
    global oblist
    xs = assoc(env, var)
    if !null(xs)
        xs.cdr = x
    elseif haskey(oblist, var)
        oblist[var] = x
    else
        error("undefind variable")
    end
end
=#

#
# 特殊形式
#
function define_f(xs, env)
    if !symbolp(xs.car)
        error("Symbol required")
    end
    oblist[xs.car] = eval_s(xs.cdr.car, env)
    xs.car
end

function if_f(xs, env)
    if eval_s(xs.car, env) !== :nil
        eval_s(xs.cdr.car, env)
    elseif !null(xs.cdr.cdr)
        eval_s(xs.cdr.cdr.car, env)
    else
        :nil
    end
end

#
# eval
#

# 引数の評価
function eval_arguments(args, env)
    a = []
    while !null(args)
        push!(a, eval_s(args.car, env))
        args = args.cdr
    end
    a
end

# 変数束縛
function add_binding(para, args, env)
    for x in args
        if !consp(para)
            error("wrong number of arguments")
        end
        env = Cons(Cons(para.car, x), env)
        para = para.cdr
    end
    if !null(para)
        error("wrong number of arguments")
    end
    env
end

# 本体の評価
function eval_body(xs, env)
    local result
    while !null(xs)
        result = eval_s(xs.car, env)
        xs = xs.cdr
    end
    result
end

# 関数適用
function apply_s(fn, args)
    if typeof(fn) == Function
        fn(args...)                 # 組み込み関数
    elseif typeof(fn) == Closure
        eval_body(fn.body, add_binding(fn.para, args, fn.env))
    else
        error("invalid function")
    end
end

function eval_s(xs, env)
    if numberp(xs)
        xs
    elseif symbolp(xs)
        lookup(xs, env)
    elseif consp(xs)
        if xs.car == :quote
            xs.cdr.car
        elseif xs.car == :define
            define_f(xs.cdr, env)
        elseif xs.car == :if
            if_f(xs.cdr, env)
        elseif xs.car == :lambda
            Closure(xs.cdr.car, xs.cdr.cdr, env)
        else
            # 関数呼び出し
            fn = eval_s(xs.car, env)
            apply_s(fn, eval_arguments(xs.cdr, env))
        end
    else
        error("unknown object")
    end
end

# REPL
function mscm()
    print("mscm> ")
    flush(STDOUT)
    nextch()
    while true
        try
            print_s(eval_s(read_s(), :nil))
            print("\nmscm> ")
        catch e
            print("ERROR: ")
            showerror(STDOUT, e)
            println("")
            # 入力のクリア
            while getch() != '\n'; nextch(); end
            print("mscm> ")
            flush(STDOUT)
        end
    end
end

# 実行
mscm()
mscm> 'a
a
mscm> 12345
12345
mscm> 1.2345
1.2345
mscm> '(1 2 3 4 5)
(1 2 3 4 5)
mscm> (car '(a b c))
a
mscm> (cdr '(a b c))
(b c)
mscm> (cons 'a 'b)
(a . b)
mscm> (pair? '(a b c))
t
mscm> (pair? 'a)
nil
mscm> (eq? 'a 'a)
t
mscm> (eq? 'a 'b)
nil
mscm> (define a 10)
a
mscm> a
10
mscm> (define square (lambda (x) (* x x)))
square
mscm> (square 123)
15129
mscm> ((lambda (x) (* x x)) 10)
100
mscm> (define fact (lambda (n) (if (= n 0) 1 (* n (fact (- n 1))))))
fact
mscm> (fact 10)
3628800
mscm> (fact 20)
2432902008176640000
mscm> (define iota (lambda (n m) (if (> n m) nil (cons n (iota (+ n 1) m)))))
iota
mscm> (iota 1 10)
(1 2 3 4 5 6 7 8 9 10)
mscm> (define map 
(lambda (f xs) (if (pair? xs)
(cons (f (car xs))
(map f (cdr xs))))))
map
mscm> (map (lambda (x) (* x x)) (iota 1 20))
(1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289 324 361 400)
mscm> (define foo (lambda (x) (lambda (y) (+ x y))))
foo
mscm> (define foo100 (foo 100))
foo100
mscm> (foo100 100)
200
mscm> (foo100 1000)
1100

Copyright (C) 2016 Makoto Hiroi
All rights reserved.

[ Home | Light | Julia ]