ナルミンチョの創作記録のロゴ

Nexcel Script仕様

これから作成する予定の、データ管理作成ソフト「Nexcel Data」のマクロ言語「Nexcel Script」の仕様の予定を書いていきます。

Hello, World!! と Fizz Buzz

まずは基本的なコードから Hello, World!!

Print "Hello, World!!"

Fizz Buzzは

for i in 1..100 Print switch when i % 15 == 0 then "FizzBuzz" when i % 3 == 0 then "Fizz" when i % 5 == 0 then "Buzz" default String(i)

こんな感じです

動作環境

Windows 10など

「Nexcel Script」が動作するのは、「Nexcel Data」だけ。汎用性は低いけど、逆を言えばそれさえダウンロードしてしまえば、すべて揃う。Windows PowerShellなどでコマンドを入力する必要はなく、わかりやすいUIで作成、管理します。確定ではないが「Nexcel Data」はユニバーサル Windows プラットフォーム (UWP) アプリで動作し、セキュリティがしっかりしているので、マクロ言語を利用したウイルスが作られることがありません

特殊な仕様

大抵のプログラミング言語のコードはテキストデータです。「Nexcel Script」は他とは違ってコードがバイナリファイルです。これによって汎用性は失われますが、テキストゆえの問題点を考えなくても良くなります。

例えば

これらの問題はバイナリファイルにして構造のみを保存しておけば解決する。インデントは構造に合わせて自動的に表示されるし、コメント以外ではキーボードの入力をそのまま受けるのではなく、こう書きたかったんだろうなと大胆な修正も可能になるし、キーボードショートカットを活用すれば特殊な入力もできる

また、純粋な関数と純粋じゃない関数をはっきり分けていることや、変数に入りうる値を計算して実行する前にエラーを出すことによってミスをかなり抑えることができる

標準出力はPrint命令です。どんな型でも(画像や音声も)出力できる

表などのデータとやり取りする場合はSet/Get命令を使います

入れ子構造はインデントで表現します

比較演算子の > => < <= ==は連続演算子として並べて使用できます。0 < x < 50で、xが0と50の間だったとき

コメント

//コメントです //複数行の コメントはインデントで 表現します

コメントにはプログラムに影響しない、情報を書くことができる。複数行コメントはインデントで表現する

型を指定しなくても、(指定をすることはできる)使われているリテラルや演算子、関数から型推論をおこないます。型を1つに特定できない場合は、その中のどれか、という制限をつけます。

for i in 0..9 // iは整数型 Print i fun plus(x) // xは文字列型 x + ";" fun threetTimes(s) // sは整数型か、文字列型 s * 3

また、「Nexcel Script」はnull安全です。nullではなくnilですが。

nilは値がないことを表す。nilは型ではなく、型に付加的につくオプション的な立ち位置。

val value0: Int? = 45 val value1: Int? = nil val bad: Int = nil //実行前エラー Print sin(rad(value0)) //問題なし Print sin(rad(value1)) //実行前エラー

なぜなら rad関数の型は{Num->Num}であり、{Num?->Num}でないからである ※Int→Numは暗黙的型変換

Int?型の変数sがnilでないことを条件判断して、nilでなかったら、dosomethingするのなら他の言語なら

if s != nil Dosomething s

と書きますが、「Nexcel Script」の仕様ではsはInt?からIntに変換されません。専用の構文が用意されていて

unwrap s Dosmething s

これでsはInt?からIntに変換されます。sがInt????だった場合は、Int???になります。unwarpも式として扱うことも可能で

Print unwarp x then x * x // xがnilでなかったら計算してnilだったらnilを返す Print unwarp x, y then x + y // 複数を指定することもできる

mapでnilを第1引数をとることはできません。

Print nil.map{ $0 * $0 } //エラー

だたし、filterMapなら、ラムダの返り値がnilだったら、配列のその要素が詰められる

Print [3,56,34,nil,232,64].filterMap{ $0 } //-> [3,56,34,232,64]

null の場合にデフォルト値を与えたいときは 次のようにする

Print x?0

Bool

trueかfalseのどちらかを表す型。リテラルはtrue falseのみ

val a: Bool = true val b = false // bはBool型 Print !a //->false Print a && b //-> false Print a || b //-> ture

Int Num

Intは整数型です。上限、下限はありません。Num型は実数です。上限、下限はありません。小数は第24位まで、0.1等の小数は10進数で保持しているので誤差は気にしなくていいです。使用するメモリは入りうる値から予想してきめます。IntからNumに暗黙の変換あり

val a = 5 val b = 8 Print a + b //-> 13 Print a - b //-> -3 Print a * b //-> 40 Print a / b //-> 1.6 ※Num型になる Print a div b //-> 1 Print 1 / 3 //-> 0.33333333333333333333333333333333 Print 0.1 * 10 //-> 1.0 Print 10 ** 50 //-> 100000000000000000000000000000000000000000000000000 Print 0b101 //->5 Print 0xff //->255

Bit8 Bit16 Bit32 Bit64 Float Double

普通は使わない。ファイルにバイナリ形式でアクセスしたり、オーバーフローを利用する場合にBit8 Bit16 Bit32 Bit64を利用する。Float Doubleは精度の低さを利用したり、する場合につかう。Nanが発生したらエラー。リテラルはない。

val a: Float = 0.1 Print a.binaryString //->"00111101110011001100110011001101"

String

文字列型です。文字コードはUTF-8をメインにする。Char型(ない)の配列ではありません。なぜかっていうと、1文字+1文字は1文字にならない場合があるからです。半角カタカナの「カ」に半角濁点「゙」を加えると「ガ」になります。「ガ」は1文字です。「あ゙゙゙゙゙゙゙゙゙゙゙」も1文字です。選択するときも一緒にしか選択できないように、単純には扱えない。だから、配列としては扱わないが、for inで使えたり[]で指定位置の文字を取り出すこともできる。そのときの1文字は「Extended Grapheme Clusters」です。別の関数を使えば、「Unicode Scalars」ずつ取り出すこともできます

val s = "Hello, World" Print s + "!!" //-> "Hello, World!!" Print "あなたは、りんごを\( 10 + 30 )個持っています" //-> "あなたは、りんごを40個持っています" Print "「FRONT-FACING BABY CHICK」\u{1F425}" //-> "「FRONT-FACING BABY CHICK」🐥" Print "abcdefg"[1] //-> "b" Print "abcdefg"[1..4] //-> "bcde" Print "abcd".reverse //-> "dcba" Print "しんぶんし".palindrome //-> true Print "a:b:c:d:e".split(":") //->["a", "b", "c", "d", "e"] Print Int("2520") //->2520 Print StringByUtf8Code("生".code and "死".code) //->"愛"

Image Color

Imageは画像を表す型。Colorは色を表す型。

var img = Image(200, 200) img =.fill(#000).line(0, 0, 99, 49, (fill: #db7e21, stroke: #24bdd1)) Print img
出力結果
出力結果(黒い背景の上に、左上に線の色が、水色で、塗りつぶしの色がオレンジの四角が書かれている)

Regex

正規表現を扱う型。

Print "abcdefgh".match(/[a-e]/) //->["a", "b", "c", "d", "e"]

Vec2 Vec3 Object3D

3Dオブジェクトとベクトル等を管理する型です。まだ、仕様がはっきり決まっていない

Print distance(from: Vec2(x: 1, y:0), to: Vec2(x: 5,y: 3)) //-> 5.0 Print Vec2(2, 9) dot Vec(3,2) //-> 24.0 Print Object3D().point(Vec3(0,0,0)).point(Vec3(5,0,0)).line(0,1) //頂点0と1を設定してそれを線で結んでいる

VImage Sound Movie WebPage

ベクター画像や音声、動画やWebページも扱える

Play Sound(File(@music,"sample.ogg")).cut(t#00:00:00, t#00:00:16) Print WebPage("//narumincho.web.fc2.com").Image.filter(/+\.(jpg|png)/) //URLから、指定したwebページの画像をすべて取得してその中から拡張子がjpgかpngのものを選び取ったものを表示する

Table Graph

「Nexcel Data」の重要な要素である。表とグラフを表す型です。

var t = Get("table1") t[35]["name"] = "ナルミンチョ" Set "table1", t

Time Date

時間と日付を表します。細かい仕様は決まっていませんが、年は1900年から始めるのではなく、Human Era(西暦に+10000年したもの)の正数で管理しようと思う。うるう日は考慮するが、うるう秒は考慮しない

Print t#05:32:45 - t#01:00:00 //-> t#04:00:00

File Folder

ファイル、フォルダーへのアクセスは制限されているので少し違った指定法で行うしかない。仕様が未定

[] Array [:] Dictionary

Array(配列)は同じ型のデータを並べて管理する型。要素数は変更できるが、その配列の要素数が固定だった場合、自動的に最適化される。

var arr = [0, 8, 329, 388] Print arr.count //-> 4 Print arr[3] //-> 388 Print arr.sum //-> 725 Print arr.avg //-> 181.25 Print arr.map { $0 * 2 } //-> [0, 16, 658, 776] Print arr.withIndex //->[(0, 0), (1, 8), (2, 329), (3, 388)] arr[1..3] = [0].repeat(3) Print arr.all(0) //-> true val b: [String] = ["A", "B", "C"] Print b.join("_") //-> "A_B_C" Print 0..10 //->[0,1,2,3,4,5,6,7,8,9,10] Print 0..<10 //->[0,1,2,3,4,5,6,7,8,9] Print (0..10).filter(even) //->[0 ,2, 4, 6, 8, 10] val dic: [String:Int] = ["Aさん": 23, "Bくん": 17, "C太郎": 44, "D子": 97] Print dic.value //-> [23, 17, 44, 97] Print dic["Aさん"] //-> 23

() Taple

型の違う変数をまとめて扱う。順番は保持する。それぞれの要素に名前を指定しないと、0からの連番でアクセスすることになる。関数の引数、戻り値に使われる。名前付き引数はタプルの名前。複数の戻り値を返すのにも使える。ただの値も要素数が1つのタプル。名前は英数字のみ。

val (a, b, c) = (1, "あ", 3.14) Print (a, b, c) //-> (1, "あ", 3.14) val d e f = (1, "あ", 3.14) Print (d, e, f) //-> ((1, "あ", 3.14), (1, "あ", 3.14), (1, "あ", 3.14)) Print d.0 //-> 1 Print d.1 //-> "あ" var leito = (name: "レイト", job: "魔法使い", hairColor: "brown") Print leito.hairColor //-> "brown" leito.hairColor = "light blue" Print leito .hairColor //-> "light blue" Print "あ".0 // "あ" Print "あ".0.0.0.0.0.0.0 // "あ" Print (1).0.0.0.0.0.0.0 // 1 val (plus,minus) = plusAndMinus(4, 15) Print plus //-> 19 Print minus //-> -11 val (, g,) = (1, 2, 3) Pirnt g //-> 2 fun plusAndMinus(x: Int,y: Int):(Int,Int) ((x + y), (x - y))

{} ラムダ式

純粋な関数を作ることができる。戻り値の型を明示的に示すことはできない。入れ子にする場合は$を使って省略して書くことはできない。変数に代入するときにvarを使うことはできない。外の変数を参照するときは、値が固定されて、後で変更しても反映されない。よってクロージャは不可能。

Print {$0 + $1}(5, 9) //-> 14 var a = 1 val fn : {Int->Int} = {$0 + a} Print fn(4) //-> 5 a = 100 Print fn(4) //-> 5 fun makeFib(a0: Int, a1: Int):{Int->Int} val fib = { switch $0 when 0 then a0 when 1 then a1 default fib($0 - 1) + fib($0 - 2) } return fib val twoTwoFib = makeFib(2, 2) Print twoTwoFib(0) //-> 2 Print twoTwoFib(1) //-> 2 Print twoTwoFib(2) //-> 4 Print twoTwoFib(3) //-> 6

変数

変数名に使えるのは、英数字のみ。先頭に数字は不可。ローワーキャメルケース。変数は2つに分かれる。varで定義したものと、valまたはパターンマッチ内で定義したものだ。varで定義したら後で変更可能だが、それ以外は変更不可。不用意な変更を防ぐために、できるだけvalを使おう。varで宣言しているのに変更してなかったら、エラーがでる。varで宣言して、値は変更できても、型は変更できない。型の特定は宣言時にできなくても良い。

val a = 6 a = 7 //->エラー var (arr, i) = ([], "愛") for i in 0..9 //iはこれでvalと宣言したのと同じになる、外側のiとは別 arr[i] = i Print arr //->[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] Print i //->"愛"

fun function

純粋な関数。同じ引数で呼ぶといつも同じ結果が返ってくる。入れ子にできる。宣言する上で使っても良い。関数名そのままで、値として扱うことができる。本体が1つの式だけならreturnを省略できる。仮引数はvalで宣言したのと同じように変更不可。仮引数名はそのまま外部引数名になる。

Print clac() //-> [55, 55] fun clac() arr = 0..10 fun sum0(nums:[Int]):Int switch 0 then return 0 default return nums[0] + sum0(mums[0..<nums.len]) fun sum1(nums:[Int]):Int switch 0 then 0 default nums[0] + sum0( mums[0..<nums.len] ) return [sum0,sum1].map{ $0(arr) } fun plus(x;Int ,y:Int) x + y Print plus(1, 5) //-> 6 Print 1.plus(5) //-> 6 Print 1 plus 5 //-> 6 Print (*)(10, 20) //->200

def Function

外部とのやり取りが必要になる関数を定義するのに使う。名前はアッパーキャメルケース。返り値がない場合は()不要。

// Print自体も外部(コンソール)とのやり取りがあるから先頭が大文字 Print "out put" // 23個と出力 PrintWithKo 23 def PrintWithKo num:Int Print "\(num)個"

if while loop

ifは条件判断による分岐。whileとloopは回数が決まらない繰り返し。ifは式にでもなる。1行で書く場合に then が必要になる。ifとwhileの判断するところはBool型でないとだめ

if "aa".len == 2 Print "「aa」は2文字" Print if (Today()-d#2017/07/02) < 1year then "1年以内" else "昔" val a = InputInt() if a>0 Print "0より大きい" elif a<0 Print "0より小さい" else Print 0と同じ" var compound = 1.0 var simple = 1.0 val simpleInterest = simple * 1.07 var year = 0 while simple <= compound year += 1 commpound *= 1.05 simple += simpleInterest Print "\(year)年で複利は単利を上回る"

for in

for inは回数が決まっている繰り返し。forとinの間はパターンマッチ。カウンタ変数を変更することはできない。inの後には配列。それぞれの要素を先頭から順番にカウンタ変数に入れていく。

val arr = [2, 56, 84, 18, 0, 65, 288] for i in arr Print i // 2 56 84 18 0 65 288 と表示される for index in arr.indices Print i // 0 1 2 3 4 5 6 と表示される for (index, value) in arr.withIndex Print (value, index) // (2, 0) (56, 1) (84, 2) (18, 3) (0, 4) (65, 5) (288, 6) と表示される for .even i in 0..9 Print i // 0, 2, 4, 6, 8と表示される for i in 0..9 by 3 Print i // 0, 3, 6, 9と表示される

switch

switchは複数の分岐が必要なときに使う。breakは不要。タプルを利用することで複数の値で分岐することができる。whenは英数字があったら、その名前の変数を参照するのに対し、caseはその名前の変数を定義する。swiftのcase valとcaseの違いと似ている。その他にwhenは .functionName で関数に入れてから、その返り値がtrueだったら、その条件を満たしたものとする。式にもできる。1行で書く場合はthenが必要、switch trueの場合はtrueを省略できる。

Print switch 1 then when 1 then "1" when 2 then "2" default "以外" //-> "1" val x = -5 switch when x < 0 Print "0より小さい" when x == 0 Print "0と同じ" when x > 0 Print "0より大きい" //->"0より小さい" var a = ("error", 504) Print switch a when ("success",) then "成功した!!" case ("error",code: Int) then "エラー エラーコードは\(code)" case (,code: String) then code when (,) then "不明なエラー" //-> "エラー エラーコードは504"

パターンマッチ

valと=の間、for inの間、whenの後、caseの後、fun name()のカッコの中などに使われる。Haskellでいう(x:xs)は[x <| xs]になる。逆に末尾を取り出したいときは[xs |> x]。空白区切りでorの意味になる。変数の場合は全てに入る

fun quicksort([x <| xs]) val smallerOrEqual = xs.filter{$0 <= x} val larger = xs.filter{$0>x} return quicksort(smallerOrEqual) ~ x ~ quicksort(larger) fun fib case 0 1 x x case x fib(x-1)+fib(x-2) Print (0..9).reduce(0 , 1], {b [|>s,t], -> b+[s+t]}) //->[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] var (a,b) = (0, 1) (a,b) = (b, a) //aとbの中身を逆にする for i in 0..9 when .odd Print "奇数\(i)" when .even Print "偶数\(i)" switch (4,6) when (4,6) (5,7) Print "(4,6)か(5,7)" when (1 2, 3 4) Print "(1,3)か(1,4)か(2,3)か(2,4)"

@ enum

@で呼び出す。ローワーキャメルケースかアッパーキャメルケースかは決まっていない

tyoedef BloodType @A @B @AB @O val a: BloodType = @A Print switch a when @A then "A型" when @B then "B型" when @AB then "AB型" when @O then "O型" //-> "A型"

typedef

classはありませんが、複数の型のデータをまとめて管理しやすくするために、typedefがある。

typedef Region {Vec2->Bool} fun circle(radius: Num): Region { sqrt( ($0.x ** 2 + $0.y ** 2)<= radius ) } fun (region: Region).move(offset:Vec2): Region { region(Vec2(x: $0.x - offset.x, y: $0.y - offset.y)) } Print circle(radius: 50).move(Vec2(x: 10,y: 20)) //->Region ({Vec2->Bool}) typedef Human (name: String, age: Int ) fun .isAdult 20 <= age val rafia = Human(name:"ラフィーア", age:16) Print rafia.isAdult //->false
ホームに戻る