言語設計

言語設計の背景にある基本概念を学びます。

Ring を選ぶ理由は?

プログラミング言語 Ring には簡明、自然な記法への試み、組織化の奨励、 および透過性とビジュアル実装があります。 簡潔なシンタックス、そして自然なインタフェースの作成を可能にする機能群、 短時間で作成・構築できる宣言型ドメイン特化言語機能を標準装備しています。 非常に小規模、高速なスマートガベージコレクターにより、 プログラマはメモリを制御下に置くことができます。 また、多種多様なプログラミングパラダイムに対応しており、便利で実用的なライブラリが付属しています。 Ring は生産性と拡張性に優れた高品質な解決方法の開発のために設計しました。

明確な設計目標

  • アプリケーション開発用のプログラミング言語です。
  • ドメイン特化ライブラリ、フレームワーク、およびツールを作成できる汎用プログラミング言語です。
  • ビジュアル・プログラミング言語 Programming Without Coding Technology (PWCT) ソフトウェアの次世代版の開発用に設計した実用プログラミング言語です。
  • 小規模・高速な言語で C/C++ プロジェクトへ組み込めます。
  • 学習と入門 (文教用途、およびコンパイラ・仮想計算機の概念) に使用できる単純明快な言語です。
  • 生産性と拡張性に優れた高品質な解決方法の開発。

簡明

Ring は非常に簡明な言語であり、非常に単純明快なシンタックスで構成しています。プログラマには、ボイラープレートコードのないプログラムの記述を奨励しています。

‘See’ 命令はメッセージを標準出力へ表示します。

See "Hello, World!"

Main 関数はオプション扱いであり、ステートメントの後に実行するため、ローカルスコープで便利です。

Func Main
        See "Hello, World!"

動的型付け、およびレキシカルスコープを使用しています。変数名の先頭に $ は不要です!

文字列の連結は‘+’演算子です。弱い型付け言語であり、文字列はコンテキストに基づいて数値と文字列との間で自動的に変換します。

nCount = 10     # グローバル変数
Func Main
        nID = 1 # ローカル変数
        See "Count = " + nCount + nl + " ID = " + nID

自然な記法への試み

Ring は英数大小文字を区別しません。

See "Enter your name ? "
Give name
See "Hello " + Name     # Name は name と同じです。

リストのインデックス (添字番号) は 1 から開始します。

aList = ["one","two","three"]
See aList[1]    # one を表示

定義前の関数呼び出し:

one()
two()
three()
Func one
        See "One" + nl
Func two
        See "two" + nl
Func three
        See "three" + nl

代入演算子は深いコピーを使用します (この操作は参照ではありません)。

aList = ["one","two","three"]
aList2 = aList
aList[1] = 1
see alist[1]    # 1 を表示
see aList2[1]   # one を表示

数値と文字列は値渡しですが、リストとオブジェクトは参照渡しです。 For in ループ でリストの項目 (アイテム、要素とも言います) を更新できます。

Func Main
        aList = [1,2,3]
        update(aList)
        see aList       # one two three を表示

Func update aList
        for x in aList
                switch x
                on 1 x = "one"
                on 2 x = "two"
                on 3 x = "three"
                off
        next

定義時のリスト使用:

aList = [ [1,2,3,4,5] , aList[1] , aList[1] ]
see aList       # 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5 を表示

一階層以上のループから脱出

for x = 1 to 10
                for y = 1 to 10
                                see "x=" + x + " y=" + y + nl
                                if x = 3 and y = 5
                                                exit 2     # 二階層のループから脱出
                                ok
                next
next

組織化の奨励

Ring ではプログラムの組織化を奨励しています。まずは関数、次にクラス、 そして、関数とヘンテコなモノと組み合わせるプログラミング言語を使用していた悪夢の日々を忘却の彼方(かなた)へ追いやります!

ソースファイルの構造は:

  • ファイルの読み込み
  • ステートメントとグローバル変数
  • 関数
  • パッケージとクラス

これにより、構成要素で end キーワードを記述しなくてもパッケージ、 クラスと関数を使用できます。

一行コメントまたは複数行コメントを使用できます。 一行コメントは # または // で始まります。 複数行コメントは /* と */ の間に記述します。

/*
        プログラムの名前    : はじめての Ring プログラム
        日付                : 2015.05.08
*/

See "What is your name? "   # 画面へメッセージを表示
give cName                  # ユーザからの標準入力を取得
see "Hello " + cName        # こんにちわ!

// See "Bye!"

簡潔なシンタックス

行の区別はしませんので、ステートメントの後に ; は不要です。または ENTER や TAB の打鍵は不要ですので、このようなコードを記述可能です。

See "The First Message" See " Another message in the same line! " + nl
See "Enter your name?" Give Name See "Hello " + Name

このコードは三種類の属性 X, Y および Z を有する Point クラスを作成します。クラス、パッケージ、関数の定義を終了するために end キーワードは使用していません。また、クラス名の直下に属性名を書くことができます。

Class Point X Y Z

定義前にクラスと関数を使用できます。 この用例では、オブジェクトの新規作成と属性の設定、および値を表示します。

o1 = New point  o1.x=10    o1.y=20   o1.z=30    See O1  Class Point X Y Z

オブジェクトの属性とメソッドへアクセスするために、ドット演算子‘.’を使用する代わりに、括弧 { } でオブジェクトへアクセスできます。その後にオブジェクトの属性とメソッドを使用できます。

o1 = New point { x=10 y=20 z=30 } See O1  Class Point X Y Z

メソッドの呼出し後に { } でオブジェクトへアクセスします。

oPerson = new Person
{
        Name = "Somebody"
        Address = "Somewhere"
        Phone = "0000000"
        Print()                 # ここでは Print() メソッドを呼び出します。
}
Class Person Name Address Phone
        Func Print
                See "Name :" + name + nl +
                        "Address :" + Address + nl +
                        "Phone : " + phone + nl

{ } で、オブジェクトへアクセスしてからオブジェクト名を記述するとき、自動的に呼び出される全ての setter/getter メソッドに対してクラスを検査します。

New Number {
                See one         # GetOne() の実行
                See two         # GetTwo() の実行
                See three       # GetThree() の実行
}
Class Number one two three
        Func GetOne
                See "Number : One" + nl
                return 1
        Func GetTwo
                See "Number : Two" + nl
                return 2
        Func GetThree
                See "Number : Three" + nl
                return 3

自然言語ステートメントの定義

{ } でオブジェクトへアクセス後に、クラスに BraceEnd() メソッドがあれば BraceEnd() メソッドを実行します!

TimeForFun = new journey
# あっと驚く!
TimeForFun {
        Hello it is me          # なんと美しいプログラミングの世界でしょう!
}
# クラス本体
Class journey
        hello=0 it=0 is=0 me=0
        func GetHello
                See "Hello" + nl
        func braceEnd
                See "Goodbye!" + nl

Eval() 関数は、文字列に記述されたコードを実行します。

cCode = "See 'Code that will be executed later!' "
Eval(cCode)     # コードを実行してメッセージを表示します。

リストの作成後に、実行用のコードをリストから生成できます。

aWords = ["hello","it","is","me"]
for word in aWords cCode=word+"=0" eval(cCode) next

Read(cFileName) 関数は、テキストファイルを読み取ります。また Write(cFileName,cString) 関数はファイルへ書き込みます。

See "Enter File Name:" Give cFileName See Read(cFileName) # ファイルの内容を表示

この用例は、二つの命令を定義するクラスの作成方法です。

  • 最初の命令は : I want window
  • 次の命令は : Window title = <式>
  • ‘the’キーワードのキーワードなどは無視できます。
New App
{
        I want window
        The window title = "hello world"
}

Class App

        # I want window 命令の属性
                i want window
                nIwantwindow = 0
        # Window title 命令の属性
        # ここでは window 属性を再定義しません。
                title
                nWindowTitle = 0
        # 値を与えると、キーワードを無視します。
                the=0

        func geti
                        if nIwantwindow = 0
                                nIwantwindow++
                        ok

        func getwant
                        if nIwantwindow = 1
                                nIwantwindow++
                        ok

        func getwindow
                        if nIwantwindow = 2
                                nIwantwindow= 0
                                see "Instruction : I want window" + nl
                        ok
                        if nWindowTitle = 0
                                nWindowTitle++
                        ok

        func settitle cValue
                        if nWindowTitle = 1
                                nWindowTitle=0
                                see "Instruction : Window Title = " + cValue + nl
                        ok

前述の用例を完了するには read() でファイルの内容を取得します。

I want window
The window title = "hello world"

そして eval() でファイルの内容を実行します!

また、 GUI ライブラリで実際のウィンドウを作成するには GetWindow() と SetTitle() メソッドを更新します。

宣言型言語の定義

自然言語ステートメントによるコードの実行、および入れ子構造によるコードの実行方法について既に学んでいます。

この用例は Web ライブラリからの引用です。 Bootstrap ライブラリで HTML ドキュメントを生成します。 この用例では、 HTML コードを直接記述せずに、類似言語を作成しています (ただの用例です)。その後、宣言型言語を使用するために入れ子構造で HTML ドキュメントを生成しています。この用例での考えかたとして GetDiv() および GetH1() メソッドは { } でアクセスできるオブジェクトを返します。各オブジェクトへのアクセス後に BraceEnd() メソッドが実行されると、生成された HTML を BraceEnd() の出力表示がルートに到達するまで親オブジェクトへ送信します。

Load "weblib.ring"
Import System.Web

Func Main

  BootStrapWebPage()
  {
        div
        {
          classname = :container
          div
          {
                classname = :jumbotron
                H1 {   text("Bootstrap Page")   }
          }
          div
          {
                classname = :row
                for x = 1 to 3
                  div
                  {
                    classname = "col-sm-4"
                    H3 { html("Welcome to the Ring programming language") }
                    P  { html("Using a scripting language is very fun!") }
                  }
                next
          }
        }
  }

このようなクラスで宣言型インタフェースを強化します。

Class Link from ObjsBase
        title  link
        Func braceend
                cOutput = nl+GetTabs() + "<a href='" +
                          Link + "'> "+ Title + " </a> " + nl

Class Div from ObjsBase
        Func braceend
                cOutput += nl+'<div'
                addattributes()
                AddStyle()
                getobjsdata()
                cOutput += nl+"</div>" + nl
                cOutput = TabMLString(cOutput)

柔軟性のあるシンタックス

様々なソースコードの記法があります!

また、言語のキーワードと演算子を変更することで、お好みの記法を作成できます!

透過型実装

Ring は透過型実装です。 コンパイラの処理段階、および仮想計算機による実行中の処理内容を把握できます。

例えば : ring helloworld.ring -tokens -rules -ic

See "Hello, World!"

実行結果

==================================================================
Tokens - Generated by the Scanner
==================================================================

   Keyword : SEE
   Literal : Hello, World!
   EndLine

==================================================================

==================================================================
Grammar Rules Used by The Parser
==================================================================

Rule : Program --> {Statement}

Line 1
Rule : Factor --> Literal
Rule : Range --> Factor
Rule : Term --> Range
Rule : Arithmetic --> Term
Rule : BitShift --> Arithmetic
Rule : BitAnd --> BitShift
Rule : BitOrXOR -->  BitAnd
Rule : Compare --> BitOrXOR
Rule : EqualOrNot --> Compare
Rule : LogicNot -> EqualOrNot
Rule : Expr --> LogicNot
Rule : Statement  --> 'See' Expr

==================================================================



==================================================================
Byte Code - Before Execution by the VM
==================================================================

         PC      OPCode        Data

          1     FuncExE
          2       PushC   Hello, World!
          3       Print
          4  ReturnNull

==================================================================

Hello, World!

ビジュアル実装

Ring は、ビジュアル・プログラミングツール Programming Without Coding Technology (PWCT) で設計しました。 Ring のビジュアルソースは、 “visualsrc” フォルダの *.ssf ファイルにあります。 生成された C 言語ソースコードは src フォルダ、 および include フォルダにあります。

このスクリーンショットは、 ring_vm.ssf ファイルからの引用です (ring_vm.c および ring_vm.h が生成されます)。

_images/visualsrc1.jpg

このスクリーンショットは、 ring_list.ssf ファイルからの引用です (ring_list.c および ring_list.h が生成されます)。

_images/visualsrc2.jpg

スマートガベージコレクター

わずらわしいメモリ操作関連の問題から解放します。

  • メモリへの不正アクセス
  • メモリリーク
  • 未初期化メモリへのアクセス
  • ダングリングポインタ

規則:

  • グローバル変数は、代入ステートメントで削除するまでメモリに存在し続けます。
  • 関数の処理終了後に、ローカル変数を削除します。
  • プログラマは、代入ステートメントでメモリから変数を削除する時期を完全に制御できます。

用例:

aList = [1,2,3,4,5]
aList = "nice"

二行目の直後、リスト [1,2,3,4,5] はメモリから削除され、文字列 “nice” が残ります。

  • プログラマは callgc() 関数を呼び出すことで、ガベージコレクターを強制実行できます。
  • 変数参照時 (関数へオブジェクト、およびリストを渡すとき)、参照カウントに基づいて変数を削除します。未参照では全て削除されますが、参照しているときはデータはメモリに残ります。

インタプリタ (VM) 全体の停止なし (GIL なし)

アプリケーションでスレッドを使用するとき、インタプリタ (VM) 全体の停止 (global interpreter (VM) lock) は起こりません (GIL なし)。

よって、スレッドは並列動作可能であり、Ring 命令は同時実行されます。

これは、スレッドと平行性において最良のものです (さらなる高速化が実現できます!)

ほとんどのアプリケーションで十分に高速動作します

プログラミング言語 Ring は単純明快、小規模、柔軟性のある最先端の設計です。また、ほとんどのアプリケーションで十分に高速動作します。

これまで、市販の電子計算機で Ring を使用してきました。下記の処理は約1秒で完了します。

  1. 100,000 行コードのコンパイル
  2. 1 ~ 10,000,000 まで数え上げる空ループの実行
  3. 100,000 項目から成るリストで最後の項目を見つけようとして、線形検索で 1000 回の検索処理を実行 (最悪値)
  4. 1,000,000 項目から成るリストを作成後にリスト全項目の合計を計算
  5. GUI アプリケーションで ListWidget へ 20,000 アイテムを追加
  6. GUI アプリケーションで TreeWidget へ 5,000 ノードを追加
  7. ターミナルのコンソールアプリケーションで 10,000 メッセージを表示

さらなる高速化を求めるならば C/C++ 拡張機能を使用できます!

用例:

? "Create list contains 100,000 items"
aList = 1:100000

? "Do 1000 search operation - Find the last item (Worst Case!)"
c = clock()

for t = 1 to 1000
        find(alist,100000)
next

? "Time: " + ( clock() - c ) / clockspersecond() + " seconds"

実行結果:

Create list contains 100,000 items
Do 1000 search operation - Find the last item (Worst Case!)
Time: 0.87 seconds

用例:

load "guilib.ring"

func main

new qApp {
        win = new qWidget() {
                move(100,100) resize(500,500)
                setWindowTitle("Many Tree Items - Testing Performance")
                tree = new qTreeWidget(win) {
                        blocksignals(True) setUpdatesEnabled(False)
                        root = new qTreeWidgetItem()
                        root.setText(0,"The Root Node")
                        t1 = clock()
                        for t = 1 to 5000
                                oItem = new qTreeWidgetItem()
                                oItem.settext(0,"Item " + t)
                                root.addchild(oItem)
                        next
                        cTime = (clock()-t1)/clockspersecond()
                        setHeaderLabel("Creating 5000 nodes in " + cTime + " seconds.")
                        addTopLevelItem(root)
                        expanditem(root)
                        blocksignals(False) setUpdatesEnabled(True)
                }
                oLayout = new qVBoxLayout() {
                        addWidget(tree)
                }
                setLayout(oLayout)
                show()
        }
        exec()
}

実行結果:

大量のツリー項目