tk555 diary

プログラミング、もしくはそれ以外のこと書きます。

QMKでファームウェアコンパイルしたらkeymap.c: error: conflicting types for 'oled_task_user' ってエラー出た

原因は自分のコードではなくファームウェア側のコードがアップデートされたため。

github.com

変更ログ的には基本的にfalseを返せばよさそう。

ちょうど自作キーボード始めたての「よーしVIA無しでもいい感じに動くようになったし、これから本格的にキーマップいじるか」って時に起こったエラーなので完全に嵌ってしまってた。

【p5js】近くから見ると青海波、遠くから見るとアインシュタイン

ってのをやりたかったけど、近くから見てもアインシュタイン

f:id:tk55513:20210918073917p:plain
うっすらアインシュタイン
f:id:tk55513:20210918074201p:plain
くっきりアインシュタイン


以下ソース

const p = {
    height: 1500,
    r: 100,
    dr:12,
    dt:5,
    ds:3,
    get strokeMax(){
        return this.strokeMin+this.ds;
    },
    get strokeMin(){
        return this.dr;
    }
}
let img;
function preload(){
    img=loadImage('data:image/jpeg;base64/*省略*/');
}
function setup() {
    angleMode(DEGREES);
    console.assert(p.dr*cos(p.dt/2)>2*p.strokeMax,'probably circle hit line')
    p['width']=img.width*p.height/img.height;
    createCanvas(p.width,p.height);
    background(220);
    for (let y = 0; y <= p.height + 100; y += p.r) {
        for (let x = 0; x <= p.width+100; x += p.r * sqrt(3)) {
            multiCircle(x,y);
        }
        const y_ = y + p.r / 2;
        for (let x = sqrt(3) * p.r / 2; x <= p.width+100; x += p.r * sqrt(3)) {
            multiCircle(x,y_);
        }
    }
    save('albert.png');
}

function draw() {

}
const multiCircle = (centerX, centerY) => {
    const dr=p.dr;
    for (let r = p.r; r > 0; r -= dr) {
        oneCircle(centerX, centerY, r);
    }
}
const oneCircle = (centerX, centerY, size) => {
    let theta = 150;
    const dt = p.dt;
    let x = centerX - size * cos(theta);
    let y = centerY - size * sin(theta);
    for (; theta > 30; theta -= dt) {
        const xx = centerX - size * cos(theta - dt);
        const yy = centerY - size * sin(theta - dt);
        const gray=color2Gray(-getAveGray((x+xx)/2,(y+yy)/2,2,2));
        strokeWeight(gray2Weight(gray));
        stroke(color(44,96,gray2narrow(gray)));
        line(x, y, xx, yy);
        [x, y] = [xx, yy];
    }
    noStroke();
    circle(centerX, centerY, (size*cos(dt/2)) *2);
    stroke(color(44,96,255));
    // stroke(1);
}
const getGray=(color)=>{
    return (color[0]+color[1]+color[2])/3.0;
}
const getAveGray=(centerX,centerY,dx,dy)=>{
    let sumGray=0.0;
    for(let x=centerX-dx;x<=centerX+dx;x+=1){
        for(let y=centerY-dy;y<=centerY+dy;y+=1){
            sumGray+=getGray(img.get(convertPos(x),convertPos(y)));
        }
    }
    return sumGray/((dx*2)*(dy*2));
}

const convertPos=(x)=>{
    return x*img.height/p.height;
}
const color2Gray=(x)=>{
    return map(x,-255,0,0,255);
}
const gray2Weight=(x)=>{
    return map(x,0,255,p.strokeMin,p.strokeMax);
}
const gray2narrow=(x)=>{
    return map(x,0,255,230,255);
}

【Python,正規表現】同じ文字が2つ以上続く単語の個数を数える

正規表現の忘備録。

空白で区切りで与えられた単語のうち同じ文字が2つ以上続く単語の個数

import re

s='apple coffee'
lst=re.findall(r'\w*(\w)(\1)',s)
print(len(lst)) # 2

正規表現がマッチするまで伸びるので同単語に'ff'と'ee'が両方入っていても後ろの方にしかマッチしない。

PowerShellで'echo a > a(1).txt'とかやりがち

経緯

powershellping 8.8.8.8 > ping(8.8.8.8).txt とかやろうとしたらちょっとハマった。
bashだとエラー吐いてくれるけどpowershellだと素直に通っちゃう。



ミス1こめ

PS> echo a > a(1).txt

とすると

PS> ls
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2020/09/22      3:33              8 a

PS> cat a
a

a(1).txtはa((1).txt)と解釈されて(1)はtxtプロパティ等を持たないので何も帰らず'a'となる。(多分)
パズルかな?


ミス2こめ

PS> echo a > a(echo a).txt

とすると

PS> ls
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2020/09/22      3:29              8 a

で中身が

PS> cat a
a

上と同様に(echo a).txtは"a".txtと解釈されてリダイレクトされるファイル名は'a'となる。$(ダラー)入れるとうまくいく。


ミス3こめ

PS> echo a > a`(echo a`).txt

とすると

PS> ls
Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2020/09/22      3:31             24 a(echo

で中身が

PS> cat '.\a(echo'
a
a).txt


これは
powershellではバッククオートで括弧をエスケープできる。
②リダイレクト後にも引数を続けて入力できる。
例:echo a > b c d e
(bという名前のファイルに'echo a c d e'の標準出力をリダイレクトする。

で結果的に
echo 'a' 'a).txt' > 'a(echo'
と同じ。

デフォルトでpowershellだとecho a a aは改行区切り。bashだと空白区切り。

おわりに

いやー難しいっすねPowerShell
素直にout-fileとかでフォーマット記法使ったほうが明確なのかとか思った。
ネットワークの勉強したかったのに時間が溶けた...

[Java]再帰降下法で四則演算・変数へ代入・標準出力に出力できるDSLを作る

再帰降下法で四則演算するプログラムはネット上にかなりあったので、それに+αするものを自分で書いてみた。


やりたいこと

const saihu=1000;
const orange=150;
const apple=200;
const amari=saihu-(orange*2+apple);
put amari;

を食べさせ標準出力に

500

と表示させたい。

BNFを決める

<program>::= <statement>*
<statement>::= (<const statement> | <put statement>) ";" "\n"*
<const statement>::= "const" <space> <var> "=" <expr>
<put statement>::= "put" <space> <expr>
<space>::= " "+
<var>::= ["a".."z","A".."Z"]+
<expr>::= <term> [('+'|'-') <term>]*
<term>::= <factor>[('*'|'/')<factor>]*
<factor> ::= <number> |'(' <expr> ')'
<number> ::=([0..9]+) | <var>

多分こんな感じ。

実装する

public class Parser{
    public static void main(String[] args)throws Exception {
        Path source=Paths.get(Parser.class.getResource("/source.txt").toURI());
        String s=Files.readAllLines(source).stream().collect(Collectors.joining("\n"));
        System.out.println(s);
        Parser p=new Parser(s);
        p.program();

        
    }
    private final Set<Character> lower=new HashSet<>(){
        {
            for(char c='a';c<='z';c++)add(c);
        }
    };
    private final Set<Character> upper=new HashSet<>(){
        {
            for(char c='A';c<='Z';c++)add(c);
        }
    };
    private final String str;
    private int cursor;
    private final Var var=new Var();

    public Parser(String str){
        this.str=str;
    }
    /**
     * カーソルを次に進める
     */
    public void next(){
        cursor++;
    }
    public int nextInt(){
        int ret=Character.getNumericValue(nextChar());
        while(hasNext()&&Character.isDigit(getChar())){
            ret*=10;
            ret+=Character.getNumericValue(nextChar());
        }
        return ret;
    }
    public String nextString(){
        StringBuilder sb=new StringBuilder();
        char c=nextChar();
        if(c==' ')throw new IllegalStateException("空白がある");
        sb.append(c);
        while(hasNext()&&getChar()!=' '){
            sb.append(nextChar());
        }
        return sb.toString();
    }
    public String getString(){
        final int originCursor=cursor;
        String s=nextString();
        cursor=originCursor;
        return s;
    }
    
    public String nextVar(){
        StringBuilder sb=new StringBuilder();
        char c=nextChar();
        if(!allowedLetterForVar(c)){
            throw new SyntaxException("変数名にならない");
        }
        sb.append(c);
        while(hasNext()&&allowedLetterForVar(getChar())){
            sb.append(nextChar());
        }
        return sb.toString();
    }
    public char getChar(){
        return str.charAt(cursor);
    }
    public char nextChar(){
        char c=getChar();
        next();
        return c;
    }
    public boolean hasNext(){
        return cursor<str.length();
    }
    public void program(){
        while(hasNext()){
            statement();
        }
    }
    public void statement(){
        if(isConstStatement()){
            constStatement();
        }else if(isPutStatement()){
            putStatement();
        }else{
            throw new SyntaxException("<const statement>でも<put statement>でもない");
        }
        char c=nextChar();
        if(c!=';'){
            throw new SyntaxException("セミコロンがない");
        }
        while(hasNext()&&getChar()=='\n'){
            next();
        }
    }
    private boolean isConstStatement(){
        return getString().equals("const");
    }
    private boolean isPutStatement(){
        return getString().equals("put");
    }
    public void constStatement(){
        String s=nextString();
        if(!s.equals("const")){
            throw new SyntaxException("const識別子がない");
        }
        space();
        String varName=var();
        char c=nextChar();
        if(c!='='){
            throw new SyntaxException("=でない");
        }
        int expr=expr();
        var.put(varName, expr);        
    }
    public void space(){
        char c=getChar();
        if(c!=' '){
            throw new SyntaxException("空白でない");
        }
        while(hasNext()&&getChar()==' '){
            next();
        }
    }
    /**
     * 変数名を返す
     */
    public String var(){
        StringBuilder sb=new StringBuilder();
        char c=nextChar();
        if(!allowedLetterForVar(c)){
            throw new SyntaxException("変数名が間違っている");
        }
        sb.append(c);
        while(hasNext()&&getChar()!='='){
            c=nextChar();
            if(!allowedLetterForVar(c)){
                throw new SyntaxException("変数名が間違っている");
            }
            sb.append(c);
        }
        return sb.toString();
    }
    private boolean allowedLetterForVar(char c){
        return lower.contains(c)||upper.contains(c);
    }
    public int expr(){
        int x=term();
        while(hasNext()){
            switch(getChar()){
                case '+':
                    next();
                    x+=term();
                    break;
                case '-':
                    next();
                    x-=term();
                    break;
                default:
                    return x;
            }
        }
        return x;
    }
    public int term(){
        int x=factor();
        while(hasNext()){
            switch(getChar()){
                case '*':
                    next();
                    x*=factor();
                    break;
                case '/':
                    next();
                    x/=factor();
                    break;
                default:
                    return x;
            }
        }
        return x;
    }
    public int factor(){
        if(getChar()=='('){
            next();
            int x=expr();
            if(getChar()!=')'){
                throw new SyntaxException("閉じかっこが見つからない");
            }
            next();
            return x;
        }
        return number();
    }
    public int number(){
        if(Character.isDigit(getChar())){
            return nextInt();
        }
        String s=nextVar();
        if(!var.has(s)){
            throw new SyntaxException("まだ存在しない変数名です");
        }
        return var.get(s);
    }
    public void putStatement(){
        String s=nextString();
        if(!s.equals("put")){
            throw new SyntaxException("put識別子がない");
        }
        space();
        int x=expr();
        System.out.println(x);
    }
}

結果

ちゃんと出力できた。 うれしい。

所感

最初はjavaccとかAntlrとか使ってみようかとか考えていたけど、この程度なら直で書いても苦にならないと感じた。 今後はラムダ式とか関数宣言とかも追加したものも作ってみたい。

[Java]enumの逆引きをバグらせた

ググった限りenumの逆引きは実際によくやる類のものらしい。

実装すると以下のようになる。

public enum OS {
        WINDOWS("win"),
        MAC("mac");

        final String s;

        private OS(String s) {
            this.s = s;
        }

        //キャッシュしておく
        private static Map<String, OS> cache = new HashMap<>() {
            {
                for (OS os : values() /*<-ここ*/) {
                    put(os.s, os);
                }
            }
        }

        public static Optional<OS> resolve(String s) {
            return Optional.ofNullable(cache.get(s));
        }
    }

実は上のコードにはバグがある。(/*ここ*/部分)

cacheの生成時、インスタンスイニシャライザの中のvaluesは実装時に意図したOS.values()(返り値OS[])ではなくHashMap.values()(返り値Collection)を呼んでしまう。

返り値が配列とCollectionのためコンパイルは通るが、その時点ではcacheの中身は空なのでバグになる。

(正直Javaパズルに入れる一歩手前くらいの難しさはあると思う)

理由が分かると簡単なミスで恥ずかしいのでQiitaじゃなくて自分のブログに書いておく。















よく考えるとstaticであるcacheの初期値が入る時点でOS.values()を呼んでいるので、enumを普通のclass的な何かだと思って脳内デバッグするとおかしなことになる。
これはenumとclassでstatic変数が初期化されるタイミングが違うため。

JavaのEnumにおける初期化の順番 | たむたむの日記 ...等々


...これハマると抜け出せないタイプの泥沼だった気がする。