Click here to Skip to main content
13,141,576 members (56,106 online)
Click here to Skip to main content
Add your own
alternative version

Stats

25.5K views
408 downloads
26 bookmarked
Posted 3 Apr 2016

The Ring Programming Language

, 6 Sep 2016
Rate this:
Please Sign up or sign in to vote.
Free Open Source Innovative and practical general-purpose multi-paradigm Scripting language developed using C/C++

Introduction

I'm interested in programming languages design and implementation and the Ring programming language (general-purpose multi-paradigm language released on January 25th, 2016) is my third project in this domain after Programming Without Coding Technology (2005-2015) and Supernova programming language (2009-2010).

In this article, I will try to introduce the language, why it's designed! and what you can do using it.

From the beginning, remember that this is an open source project that you can get, use and modify for free (MIT License). If you want to contribute or get the source code, just check the project source code (GitHub) and the language website for more resources like (documentation and support group).

Also, you can download Ring 1.0 for Windows (Binary Release) or Ring 1.0 for Ubuntu Linux (Binary Release).

Also we have Ring 1.0 for Mac OS X (Binary Release) and Ring 1.0 for Mobile App Development using Qt

Background

In November, 2011, I started to think about creating a new version of the Programming Without Coding Technology (PWCT) software from scratch. I was interested in creating multi-platform edition of the software beside adding support for Web & Mobile development. Most of the PWCT source code was written in VFP (Microsoft Visual FoxPro 9.0 SP2) and the software comes with a simple scripting language for creating the components called (RPWI). The software contains components that support code generation in programming languages like Harbour, C, Supernova & Python.

What I was looking for is a programming language that can be used to build the development environment, provides multi-platform support, more productivity, better performance, can be used for components scripting & can be used for developing different kinds of applications. Instead of using a mix of programming languages, I decided to use one programming language for creating the development environment, for components scripting & for creating the applications.

I looked at many programming languages like C, C++, Java, C#, Lua, PHP, Python & Ruby. I avoided using C or C++ directly because I want high-level of productivity more than the level provided by these languages, also a language behind visual programming environment for novice programmers or professionals must be easy to use & productive. Java & C# are avoided for some reason too! I wanted to use a dynamic programming language and these languages are static typing, Java is multi-platform, also C# through Mono, but the use of huge number of classes and forcing the use of Object-Orientation, using a verbose language is not right for me. I need a small language, but fast and productive, also I need better control on the Garbage Collector (GC), I need a better one that is designed for fast applications.

Lua is small and fast, but it’s avoided because I need more powerful language for large applications. PHP is a Web programming language and it’s syntax is very similar to C, this leads to a language not general as I want and not simple as I need to have. Python & Ruby are more like what I need, but I need something more simple, smaller, faster & productive. Python and Ruby are Case-Sensitive, the list index start counting from 0, you have to define the function before calling it, Ruby usage of Object-Orientation and message passing is more than what I need and decrease performance, Python syntax (indentation, using self, :, pass & _) is not good for my goals.

All of these languages are successful languages, and very good for their domains, but what I need is a different language that comes with new ideas and intelligent implementation (Innovative, Ready, Simple, Small, Flexible and Fast).

The Ring is an Innovative and practical general-purpose multi-paradigm scripting language that can be embedded in C/C++ projects, extended using C/C++ code and/or used as standalone language. The supported programming paradigms are Imperative, Procedural, Object-Oriented, Functional, Meta programming, Declarative programming using nested structures, and Natural programming. The language is portable (Windows, Linux, Mac OS X, Android, etc.) and can be used to create Console, GUI, Web, Games and Mobile applications. The language is designed to be Simple, Small, Flexible and Fast. Its Dynamic Language (Dynamic Typing and Weakly Typed) that compile the source code to byte code then execute it by the Ring Virtual Machine, which is integrated with the Ring Compiler in one program. The first version of the language (around 100,000 lines of C/C++/Ring code) is released on January 25th, 2016

Why Ring?

The language is simple, trying to be natural, encourage organization and comes with transparent and visual implementation. It comes with compact syntax and a group of features that enable the programmer to create natural interfaces and declarative domain-specific languages in a fraction of time. It is very small, fast and comes with smart garbage collector that puts the memory under the programmer control. It supports many programming paradigms, comes with useful and practical libraries. The language is designed for productivity and developing high quality solutions that can scale.

The language is designed for a clear goal:

  • Applications programming language
  • Productivity and developing high quality solutions that can scale
  • Small and fast language that can be embedded in C/C++ projects
  • Simple language that can be used in education and introducing Compiler/VM concepts
  • General-Purpose language that can be used for creating domain-specific libraries, frameworks and tools
  • Practical language designed for creating the next version of the Programming Without Coding Technology software

Using the Code

I have 10 samples to introduce, you can run all of these samples using the online version provided by the Ring website.

Sure I will start with the hello world program!

See "Hello, World!"

The Ring uses (See) and (Give) for printing output and getting input from the user.

The language uses special keywords (different from other languages). These special words selected to be (small and fast to write and still with clear meaning). Anyway, if you are going to add the language to your project, you can hack the source code and modify anything!

This from the beginning demonstrates that Ring is not a clone from any other language but sure the language borrows some ideas from other languages like C,C++, C#, Java, PHP, Python, Ruby, Lua, Basic, Supernova and Harbour. Also the language coms with new ideas specially for abstraction and creating natural and declarative interfaces to be used as domain-specific languages. Also, the language implementation (transparent and visual) helps in using it in Compiler courses. Being a multiparadigm language that is fast and written in ANSI C also provides a good chance for embedding the language in C/C++ projects.

The language is not case sensitive, you can write "SEE", "see" or "See".

The Main function is optional and will be executed after the statements, and is useful for using the local scope.

The Ring Uses Dynamic Typing and Lexical scoping. No $ is required before the variable name!
You can use the '+' operator for string concatenation and the language is weakly typed and will convert automatically between numbers and strings based on the context.

The list index starts from 1. You can call functions before definition. The assignment operator uses Deep copy (no references in this operation). You can pass numbers and strings by value, but pass lists and objects by reference.
The for in loop can update the list items. We can use Lists during definition as in the next example.

aList = [ [1,2,3,4,5] , aList[1] , aList[1] ]
see aList # print 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5

We can easily exit from more than one loop.

for x = 1 to 10
        for y = 1 to 10
                see "x=" + x + " y=" + y + nl
                if x = 3 and y = 5
                        exit 2     # exit from 2 loops
                ok
        next
next

The language encourages organization, forget bad days using languages where the programmer starts with function then class then function and a strange mix between things! Each source file follows the next structure:

  • Load Files
  • Statements and Global Variables
  • Functions
  • Packages and Classes

This enables us to use Packages, Classes and Functions without the need to use a keyword to end these components.

We can write one line comments and multi-line comments
The comment starts with # or //.
Multi-line comments are written between /* and */.

Ring comes with transparent implementation. We can know what is happening in each compiler stage and what is going on during the run-time by the Virtual Machine Example: ring helloworld.ring -tokens -rules -ic

==================================================================
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!

The Ring programming language is designed using the PWCT visual programming tool and you will find the visual source of the language in the folder "visualsrc" - *.ssf files and the generated source code (In the C Language) in the src folder and the include folder. Fork me on GitHub.

The next screen shot demonstrates what I mean by visual implementation:

The language is not line sensitive, you don't need to write ; after statements, also you don't need to press ENTER or TAB, so we can write the next code:

See "The First Message"	See " Another message in the same line! " + nl

The next code create a class called Point that contains three attributes X, Y and Z. No keywords are used to end the package/class/function definition. Also, we can write the attributes names directly below the class name.

We can use classes and functions before their definition, In this example, we will create new object, set the object attributes, then print the object values.

Instead of using the dot '.' operator to access the object attributes and methods, we can use braces { } to access the object, then we can use the object attributes and methods.

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

Now, we will call a method after accessing the object using { }.

oPerson = new Person
{
	Name = "Somebody"
	Address = "Somewhere"
	Phone = "0000000"
	Print()			# here we call the Print() method
}
Class Person Name Address Phone
	Func Print
		See "Name :" + name + nl +
		    "Address :" + Address + nl +
		    "Phone : " + phone + nl

When we use { } to access the object, then write any attribute name, the language will check the class for any setter/getter methods that will be called automatically.

After the object access using { } if the class contains a method called BraceEnd(), it will be executed!

TimeForFun = new journey
# The first surprise!
TimeForFun {
	Hello it is me		# What a beatiful programming world!
}
# Our Class
Class journey
    hello=0 it=0 is=0 me=0
	func GetHello
		See "Hello" + nl
	func braceEnd
		See "Goodbye!" + nl

The next example presents how to create a class that defines two instructions:

  • The first instruction is: I want window
  • The second instruction is: Window title = Expression

Also keywords that can be ignored like the ‘the’ keyword

New App
{
        I want window
        The window title = "hello world"
}

Class App

        # Attributes for the instruction I want window 
                i want window 
                nIwantwindow = 0 
        # Attributes for the instruction Window title 
        # Here we don't define the window attribute again 
                title 
                nWindowTitle = 0 
        # Keywords to ignore, just give them any value 
                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

We learned how to use Natural statements to execute our code and using the same features, we can use nested structures to execute our code.

The next example from the Web library, generates HTML document using the Bootstrap library. No HTML code is written directly in this example, we created a similar language (just as example). Then, using this declarative language that uses nested structures, we generated the HTML Document.

The idea in this example is that the GetDiv() and GetH1() methods return an object that we can access using {} and after each object access the method BraceEnd() will be executed to send the generated HTML to the parent object until we reach to the root where BraceEnd() will print the output.

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
          }
        }
  }

The classes that power the declarative interface looks like this:

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)

Embedding the Ring Programming Language in C/C++ Projects

We can use the Ring language from C/C++ programs using the next functions:

RingState *ring_state_init();
ring_state_runcode(RingState *pState,const char *cCode);
ring_state_delete(RingState *pState);

The idea is to use the ring_state_init() to create new state for the Ring Interpreter, then call the ring_state_runcode() function to execute Ring code using the same state. When we are done, we call the ring_state_delete() to free the memory.

#include "ring.h"
#include "stdlib.h"
int main(int argc, char *argv[])
{
  RingState *pState = ring_state_init();
  printf("welcome\n");
  ring_state_runcode(pState,"see 'hello world from the ring programming language'+nl");
  ring_state_delete(pState);
}

Sure the previous sample can't be tested using the online version of the language where you will need a C compiler + the Ring language header files and library (that you can build from the source code)

The Ring API comes with the next functions to create and delete the state. Also we have functions to create new variables and get variables values.

RingState * ring_state_init ( void ) ;
RingState * ring_state_delete ( RingState *pRingState ) ;
void ring_state_runcode ( RingState *pRingState,const char *cStr ) ;
List * ring_state_findvar ( RingState *pRingState,const char *cStr ) ;
List * ring_state_newvar ( RingState *pRingState,const char *cStr ) ;
void ring_state_main ( int argc, char *argv[] ) ;
void ring_state_runfile ( RingState *pRingState,const char *cFileName ) ;

We can create more than one ring state in the same program and we can create and modify variable values.

To get the variable list, we can use the ring_state_findvar() function.

To create new variable, we can use the ring_state_newvar() function.

Example:

#include "ring.h"
#include "stdlib.h"

int main(int argc, char *argv[])
{
  List *pList;

  RingState *pState = ring_state_init();
  RingState *pState2 = ring_state_init();

  printf("welcome\n");
  ring_state_runcode
  (pState,"see 'hello world from the ring programming language'+nl");

  printf("Again from C we will call ring code\n");
  ring_state_runcode(pState,"for x = 1 to 10 see x + nl next");

  ring_state_runcode(pState2,"for x = 1 to 5 see x + nl next");

  printf("Now we will display the x variable value from ring code\n");
  ring_state_runcode(pState,"see 'x value : ' + x + nl ");
  ring_state_runcode(pState2,"see 'x value : ' + x + nl ");

  pList = ring_state_findvar(pState,"x");

  printf("Printing Ring variable value from C , %.0f\n",
                ring_list_getdouble(pList,RING_VAR_VALUE));

  printf("now we will set the ring variable value from C\n");
  ring_list_setdouble(pList,RING_VAR_VALUE,20);

  ring_state_runcode(pState,"see 'x value after update : ' + x + nl ");

  pList = ring_state_newvar(pState,"v1");
  ring_list_setdouble(pList,RING_VAR_VALUE,10);

  pList = ring_state_newvar(pState,"v2");
  ring_list_setdouble(pList,RING_VAR_VALUE,20);

  ring_state_runcode(pState,"see 'v1 + v2 = ' see v1+v2 see nl");

  ring_state_runcode(pState,"see 'end of test' + nl");

  ring_state_delete(pState);
  ring_state_delete(pState2);
}

Output:

welcome
hello world from the ring programming language
Again from C we will call ring code
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
Now we will display the x variable value from ring code
x value : 11
x value : 6
Printing Ring variable value from C , 11
now we will set the ring variable value from C
x value after update : 20
v1 + v2 = 30
end of test

Ring VM Extension using C/C++ Code

We can extend the Ring Virtual Machine (RingVM) by adding new functions written in the C programming language or C++. The RingVM comes with many functions written in C that we can call like any Ring function.

We can extend the language by writing new functions then rebuilding the RingVM again, or we can create shared library (DLL/So) file to extend the RingVM without the need to rebuild it.

Each module function may contain the next steps:

  1. Check Parameters Count
  2. Check Parameters Type
  3. Get Parameters Values
  4. Code/Call Functions
  5. Return Value

The structure is very similar to any function (Input - Process - Output). But here, we will use the Ring API for the steps 1, 2, 3 and 5.

The next code represents the sin() function implementation using the Ring API and the sin() C function.

void ring_vm_math_sin ( void *pPointer )
{
        if ( RING_API_PARACOUNT != 1 ) {
                RING_API_ERROR(RING_API_MISS1PARA);
                return ;
        }
        if ( RING_API_ISNUMBER(1) ) {
                RING_API_RETNUMBER(sin(RING_API_GETNUMBER(1)));
        } else {
                RING_API_ERROR(RING_API_BADPARATYPE);
        }
}

You can read more about extension from this tutorial.

Also, you can use a code generator that comes with Ring to quickly generate wrappers for C/C++ functions/classes.

Editors Support

You can use the language with any editor, also the Ring team provides extensions for Notepad++, SubLime Text 2, Geany Editor, Visual Studio and Atom editor.

The next screen shot demonstrates using Ring in SubLime Text 2.

The next screen shot demonstrates using Ring in Atom (my favorite code editor).

Graphics and 2D Games Programming using RingAllegro

You can learn about using the Allegro game programming library from the Ring language through this tutorial.

The next example displays and rotates an image:

Load "gamelib.ring"

al_init()
al_init_image_addon()

display = al_create_display(640,480)
al_set_target_bitmap(al_get_backbuffer(display))
al_clear_to_color(al_map_rgb(255,255,255))

image = al_load_bitmap("man2.jpg")
al_draw_rotated_bitmap(image,0,0,250,250,150,0)
al_draw_scaled_bitmap(image,0,0,250,250,20,20,400,400,0)

al_flip_display()
al_rest(2)

al_destroy_bitmap(image)
al_destroy_display(display)

The next screen shot demonstrates the application during the runtime:

Web Development

For now, the Ring programming language comes with a simple CGI Library for creating web applications.

Hello World program:

#!b:\ring\ring.exe -cgi

Load "weblib.ring"

Import System.Web

WebPage()
{
        Text("Hello World!")
}

The library is written in Ring (around 5K lines of code).

The next screen shot demonstrates the power of the library that uses Object-Oriented Programming and the MVC design pattern.

You can read more about the library from this tutorial.

Desktop and Mobile Applications (Using RingQt)

The next example asks the user about his/her name, then says Hello!

Load "guilib.ring"

MyApp = New qApp {

        win1 = new qWidget() {

                setwindowtitle("Hello World")
                setGeometry(100,100,400,130)
                label1 = new qLabel(win1) {
                        settext("What is your name ?")
                        setGeometry(10,20,350,30)
                        setalignment(Qt_AlignHCenter)
                }
                btn1 = new qpushbutton(win1) {
                        setGeometry(10,200,100,30)
                        settext("Say Hello")
                        setclickevent("pHello()")
                }
                btn2 = new qpushbutton(win1) {
                        setGeometry(150,200,100,30)
                        settext("Close")
                        setclickevent("pClose()")
                }
                lineedit1 = new qlineedit(win1) {
                        setGeometry(10,100,350,30)
                }
                layout1 = new qVBoxLayout() {
                        addwidget(label1)
                        addwidget(lineedit1)
                        addwidget(btn1)
                        addwidget(btn2)
                }
                win1.setlayout(layout1)
                show()
        }

        exec()
}

Func pHello
        lineedit1.settext( "Hello " + lineedit1.text())

Func pClose
        MyApp.quit()

The next screen shot demonstrates the application during the runtime.

You can read more about GUI development using Ring from this tutorial.

The tutorial comes with many samples including a simple Cards game developed using RingQt where each player get 5 cards, the cards are unknown to any one. Each time one player clicks on one card to see it, if the card is identical to another card, the play gets points for each card. If the card value is “5”, the player get points for all visible cards.

The next screen shot while running the game using a Mobile (Android):

Points of Interest

The Ring is a multi-paradigm language that will give you the choice to select the right paradigm for your problem.

Try the language (It's FREE OPEN SOURCE), then think about creating new projects (frameworks) based on the language power (declarative programming and natural programming) and I'm sure that you will get success.

We need contributors so feel free to join and help us (improve our code, add features, report bugs, fix some bugs!, write libraries, provide extensions, write documentation, etc.)

History

  • January 25, 2016: Ring 1.0 released!
  • September, 2015: Most of the documentation was written!
  • August, 2015: Most of the testing was done!
  • May, 2015: Most of the work in the compiler and The Virtual Machine was done!
  • April, 2015: The language name: Ring
  • September, 2013: The design and the implementation of the language started!
  • November, 2011: The ideas and the general goals behind the new programming language

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

No Biography provided

You may also be interested in...

Comments and Discussions

 
QuestionOne Suggestion Pin
Rick York4-May-17 6:12
memberRick York4-May-17 6:12 
AnswerRe: One Suggestion Pin
Mahmoud Samir Fayed4-May-17 15:39
memberMahmoud Samir Fayed4-May-17 15:39 
QuestionLoop exiting Pin
leon de boer2-Mar-17 21:05
memberleon de boer2-Mar-17 21:05 
AnswerRe: Loop exiting Pin
Mahmoud Samir Fayed2-Mar-17 22:29
memberMahmoud Samir Fayed2-Mar-17 22:29 
GeneralRe: Loop exiting Pin
leon de boer2-Mar-17 23:37
memberleon de boer2-Mar-17 23:37 
GeneralRe: Loop exiting Pin
Mahmoud Samir Fayed2-Mar-17 23:44
memberMahmoud Samir Fayed2-Mar-17 23:44 
Generalmy vote of 5 Pin
Southmountain25-Feb-17 5:22
memberSouthmountain25-Feb-17 5:22 
GeneralRe: my vote of 5 Pin
Mahmoud Samir Fayed25-Feb-17 9:01
memberMahmoud Samir Fayed25-Feb-17 9:01 
QuestionMentioned relation between VFP (sp2) and Ring Pin
Frank Kropp26-Nov-16 1:12
memberFrank Kropp26-Nov-16 1:12 
AnswerRe: Mentioned relation between VFP (sp2) and Ring Pin
Mahmoud Samir Fayed29-Nov-16 5:09
memberMahmoud Samir Fayed29-Nov-16 5:09 
GeneralMy vote of 5 Pin
Mr. Tamer Radwan15-Oct-16 22:16
memberMr. Tamer Radwan15-Oct-16 22:16 
GeneralRe: My vote of 5 Pin
Mahmoud Samir Fayed16-Oct-16 21:57
memberMahmoud Samir Fayed16-Oct-16 21:57 
QuestionBad verbs Pin
David O'Neil8-Sep-16 6:31
memberDavid O'Neil8-Sep-16 6:31 
AnswerRe: Bad verbs Pin
Mahmoud Samir Fayed8-Sep-16 14:57
memberMahmoud Samir Fayed8-Sep-16 14:57 
GeneralRe: Bad verbs Pin
Bhudda8-Sep-16 19:38
memberBhudda8-Sep-16 19:38 
GeneralRe: Bad verbs Pin
Mahmoud Samir Fayed8-Sep-16 21:20
memberMahmoud Samir Fayed8-Sep-16 21:20 
GeneralRe: Bad verbs Pin
David O'Neil8-Sep-16 18:21
memberDavid O'Neil8-Sep-16 18:21 
GeneralRe: Bad verbs Pin
Mahmoud Samir Fayed8-Sep-16 18:41
memberMahmoud Samir Fayed8-Sep-16 18:41 
GeneralRe: Bad verbs Pin
David O'Neil9-Sep-16 8:53
memberDavid O'Neil9-Sep-16 8:53 
GeneralRe: Bad verbs Pin
Mahmoud Samir Fayed9-Sep-16 13:51
memberMahmoud Samir Fayed9-Sep-16 13:51 
QuestionExcellent, My vote of 5 Pin
Liju Sankar13-May-16 11:06
professionalLiju Sankar13-May-16 11:06 
AnswerRe: Excellent, My vote of 5 Pin
Mahmoud Samir Fayed13-May-16 13:25
memberMahmoud Samir Fayed13-May-16 13:25 
GeneralMy vote of 5 Pin
eslipak4-Apr-16 11:49
professionaleslipak4-Apr-16 11:49 
GeneralRe: My vote of 5 Pin
Mahmoud Samir Fayed4-Apr-16 19:26
memberMahmoud Samir Fayed4-Apr-16 19:26 
GeneralMy vote of 5 Pin
Matth Moestl3-Apr-16 9:47
professionalMatth Moestl3-Apr-16 9:47 
GeneralRe: My vote of 5 Pin
Mahmoud Samir Fayed3-Apr-16 19:00
memberMahmoud Samir Fayed3-Apr-16 19:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.170915.1 | Last Updated 6 Sep 2016
Article Copyright 2016 by Mahmoud Samir Fayed
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid