diff --git a/ExternalCode.md b/ExternalCode.md new file mode 100644 index 0000000..db77204 --- /dev/null +++ b/ExternalCode.md @@ -0,0 +1,25 @@ +# Run external code ? + +In a standard Dotnet Interactive Notebook, you can use the `#!import` magic command, to load an run some code that is stored in a file. + +The trouble is that the file must contain code that is known by the original Dotnet Interactive, and the call to the right execution engine is based on the file extension : +- .cs file will run using the C# engine +- .ps1 file will run using the Powershell engine +etc + +Unfortunatly, XSharp code is stored in a .prg file, that the `#!import` magic command will not recognize. + +# Workaround + +But ... Dotnet Interactive is able to `#!import` an external Notebook !! :) + +So, the solution is to put your external code into a Notebook (either .ipynb or .dib). +In this Notebook, add some code cells. Maybe one cell in csharp script to load the XSharpInteractive engine, then another cell with all your XSharp code. + +Then, in your current Notebook you can write : + + #!import pathToYour/NotebookToImport.dib +And below the ***new*** code that can use the elements you have defined in the *imported* Notebook. + +Imagine: +you can have your classes definitions in some external Notebook, and use it in your current Notebook. diff --git a/FirstSteps/15-Methods_and_Exceptions.ipynb b/FirstSteps/15-Methods_and_Exceptions.ipynb index 2aca410..43f3ff9 100644 --- a/FirstSteps/15-Methods_and_Exceptions.ipynb +++ b/FirstSteps/15-Methods_and_Exceptions.ipynb @@ -18,44 +18,7 @@ "kernelName": "csharp" } }, - "outputs": [ - { - "data": { - "text/html": [ - "
Installed Packages
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "Loading extension script from `C:\\Users\\fabri\\.nuget\\packages\\xsharpinteractive\\1.0.0\\interactive-extensions\\dotnet\\extension.dib`" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
Loading XSharp Interactive...
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
XSharpKernelVSExtension has been loaded successfully. It support XSharp Core Dialect language scripting. Try it by running: #!xsharpOr by selecting xsharp as language for the Notebook cell.
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "// <-= Press on the Arrow to run Me\n", "#r \"nuget:XSharpInteractive\"" @@ -85,17 +48,7 @@ "kernelName": "xsharp" } }, - "outputs": [ - { - "data": { - "text/html": [ - "


" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "CLASS Transaction\n", " // Properties\n", @@ -254,7 +207,7 @@ "But, as you are providing some code to manage a BankAccount and its Transactions, your code can throw some exceptions when something goes wrong.\n", "\n", "So, what if someone tries to deposit negative money? \n", - "It doesn't make sense, but currently the method allows for that. What you can do is make an exception. \n", + "It doesn't make sense, but currently the method allows for that : What you can do is throwing an exception. \n", "Before doing anything, you check that the amount deposited is more than 0. If it is, great, the code moves on to adding the transaction. If not, the code throws an exception, where it stops the code and prints out the issue.\n", "\n", "> Place this code in the very beginning of the `MakeDeposit` method.\n", @@ -266,7 +219,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": { "dotnet_interactive": { "language": "xsharp" @@ -275,17 +228,7 @@ "kernelName": "xsharp" } }, - "outputs": [ - { - "data": { - "text/html": [ - "


" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "using System.Collections.Generic\n", "\n", @@ -325,6 +268,369 @@ " END METHOD\n", "END CLASS" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 2.1 But what happens after throwing an `Exception` ?\n", + "\n", + "If you did **not** provide some code to handle the `Exception`, the .NET Runtime will get it and show an Error message with the text of the Exception. \n", + "\n", + "But you can also prepare that situation in your code. \n", + "To do so, you will create this kind of architecture : \n", + "\n", + " TRY \n", + " // Do stuff\n", + " CATCH e AS Exception \n", + " // Provide some code to handle the error\n", + " // It can be a Message Window, a logging system, or some default action to ignore the Error\n", + " FINALLY \n", + " // Clean up\n", + " END TRY\n", + "\n", + "Please note that the `FINALLY` block is not mandatory, but can be usefull as it execute in all situations : With or Without an Exception.\n", + "\n", + "*At the end of this serie of Notebooks, you will have the opportunity to create your full X# application based on our/your code. That construction might be helpfull in several points.*\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 3: Add Withdrawal\n", + "Now, we will add some code to allow Withdrawal.\n", + "\n", + "> Add this code to MakeWithdrawal.\n", + "\n", + " IF (amount <= 0)\n", + " THROW ArgumentOutOfRangeException{ nameof(amount), \"Amount of withdrawal must be positive\" }\n", + " ENDIF\n", + " IF (Balance - amount < 0)\n", + " THROW InvalidOperationException{ \"Not sufficient funds for this withdrawal\" }\n", + " ENDIF\n", + " var withdrawal = Transaction{-amount, withdrawDate, notes}\n", + " allTransactions:Add(withdrawal)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "xsharp" + }, + "polyglot_notebook": { + "kernelName": "xsharp" + } + }, + "outputs": [], + "source": [ + "using System.Collections.Generic\n", + "\n", + "CLASS BankAccount\n", + " // Properties\n", + " PUBLIC PROPERTY Number AS STRING AUTO GET SET\n", + " PUBLIC PROPERTY Owner AS STRING AUTO GET SET\n", + " PUBLIC PROPERTY Balance AS Decimal\n", + " GET\n", + " VAR currentBalance := 0.0M\n", + " FOREACH VAR item IN SELF:allTransactions\n", + " currentBalance += item:Amount\n", + " NEXT\n", + " RETURN currentBalance\n", + " END GET\n", + " END PROPERTY\n", + " PRIVATE STATIC accountBaseNumber := 1234567890 AS INT\n", + " PRIVATE allTransactions := List{} AS List\n", + " \n", + " // Constructor\n", + " PUBLIC CONSTRUCTOR( name AS STRING, initialBalance AS DECIMAL )\n", + " SELF:Owner := name\n", + " SELF:Number := accountBaseNumber:ToString()\n", + " accountBaseNumber ++\n", + " END CONSTRUCTOR\n", + " \n", + " // Methods\n", + " PUBLIC METHOD MakeDeposit( amount AS DECIMAL, depositDate AS DateTime, notes AS STRING ) AS VOID\n", + " // \n", + " IF (amount <= 0)\n", + " THROW ArgumentOutOfRangeException{ nameof(amount), \"Amount of deposit must be positive\" }\n", + " ENDIF \n", + " var deposit := Transaction{ amount, depositDate, notes }\n", + " allTransactions:Add(deposit)\n", + " END METHOD\n", + "\n", + " PUBLIC METHOD MakeWithdrawal( amount AS DECIMAL, withdrawDate AS DateTime, notes AS STRING ) AS VOID\n", + " // Add code below\n", + " \n", + " END METHOD\n", + "END CLASS" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 4: Creating initial deposit\n", + "Now that you can do deposits and withdrawals, you can finally make an initial deposit again. \n", + "\n", + "What you'll do is create a deposit of the initial amount when you're first constructing the bank account.\n", + "\n", + "> Add this code to the BankAccount constructor.\n", + "\n", + " SELF:MakeDeposit(initialBalance, DateTime.Now, \"Initial balance\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "xsharp" + }, + "polyglot_notebook": { + "kernelName": "xsharp" + } + }, + "outputs": [], + "source": [ + "using System.Collections.Generic\n", + "\n", + "CLASS BankAccount\n", + " // Properties\n", + " PUBLIC PROPERTY Number AS STRING AUTO GET SET\n", + " PUBLIC PROPERTY Owner AS STRING AUTO GET SET\n", + " PUBLIC PROPERTY Balance AS Decimal\n", + " GET\n", + " VAR currentBalance := 0.0M\n", + " FOREACH VAR item IN SELF:allTransactions\n", + " currentBalance += item:Amount\n", + " NEXT\n", + " RETURN currentBalance\n", + " END GET\n", + " END PROPERTY\n", + " PRIVATE STATIC accountBaseNumber := 1234567890 AS INT\n", + " PRIVATE allTransactions := List{} AS List\n", + " \n", + " // Constructor\n", + " PUBLIC CONSTRUCTOR( name AS STRING, initialBalance AS DECIMAL )\n", + " SELF:Owner := name\n", + " SELF:Number := accountBaseNumber:ToString()\n", + " accountBaseNumber ++\n", + " // Add initial deposit code here\n", + " \n", + " END CONSTRUCTOR\n", + " \n", + " // Methods\n", + " PUBLIC METHOD MakeDeposit( amount AS DECIMAL, depositDate AS DateTime, notes AS STRING ) AS VOID\n", + " // \n", + " IF (amount <= 0)\n", + " THROW ArgumentOutOfRangeException{ nameof(amount), \"Amount of deposit must be positive\" }\n", + " ENDIF \n", + " var deposit := Transaction{ amount, depositDate, notes }\n", + " allTransactions:Add(deposit)\n", + " END METHOD\n", + "\n", + " PUBLIC METHOD MakeWithdrawal( amount AS DECIMAL, withdrawDate AS DateTime, notes AS STRING ) AS VOID\n", + " // \n", + " IF (amount <= 0)\n", + " THROW ArgumentOutOfRangeException{ nameof(amount), \"Amount of withdrawal must be positive\" }\n", + " ENDIF\n", + " IF (Balance - amount < 0)\n", + " THROW InvalidOperationException{ \"Not sufficient funds for this withdrawal\" }\n", + " ENDIF\n", + " var withdrawal = Transaction{-amount, withdrawDate, notes}\n", + " allTransactions:Add(withdrawal)\n", + " END METHOD\n", + "END CLASS" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Create and test the Application\n", + "\n", + "You will find the whole code in the block below.\n", + "Then, we have added some lines in the test code, because you can now make deposits and withdrawals. Test it out!\n", + "\n", + "> Run the following cells, including the new stuff in the test code.\n", + "> Make your own deposit and withdrawal. " + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "dotnet_interactive": { + "language": "xsharp" + }, + "polyglot_notebook": { + "kernelName": "xsharp" + } + }, + "outputs": [], + "source": [ + "CLASS Transaction\n", + " // Properties\n", + " PUBLIC PROPERTY Amount AS DECIMAL AUTO GET\n", + " PUBLIC PROPERTY Date AS DateTime AUTO GET\n", + " PUBLIC PROPERTY Notes AS STRING AUTO GET\n", + " \n", + " // Constructor \n", + " PUBLIC CONSTRUCTOR( trAmount AS Decimal, trDate AS DateTime, trNote AS String )\n", + " SELF:Amount := trAmount\n", + " SELF:Date := trDate\n", + " SELF:Notes := trNote\n", + " END CONSTRUCTOR\n", + "\n", + "END CLASS\n", + "\n", + "using System.Collections.Generic\n", + "\n", + "CLASS BankAccount\n", + " // Properties\n", + " PUBLIC PROPERTY Number AS STRING AUTO GET SET\n", + " PUBLIC PROPERTY Owner AS STRING AUTO GET SET\n", + " PUBLIC PROPERTY Balance AS Decimal\n", + " GET\n", + " VAR currentBalance := 0.0M\n", + " FOREACH VAR item IN SELF:allTransactions\n", + " currentBalance += item:Amount\n", + " NEXT\n", + " RETURN currentBalance\n", + " END GET\n", + " END PROPERTY\n", + " PRIVATE STATIC accountBaseNumber := 1234567890 AS INT\n", + " PRIVATE allTransactions := List{} AS List\n", + " \n", + " // Constructor\n", + " PUBLIC CONSTRUCTOR( name AS STRING, initialBalance AS DECIMAL )\n", + " SELF:Owner := name\n", + " SELF:Number := accountBaseNumber:ToString()\n", + " accountBaseNumber ++\n", + " // \n", + " SELF:MakeDeposit(initialBalance, DateTime.Now, \"Initial balance\")\n", + " END CONSTRUCTOR\n", + " \n", + " // Methods\n", + " PUBLIC METHOD MakeDeposit( amount AS DECIMAL, depositDate AS DateTime, notes AS STRING ) AS VOID\n", + " // \n", + " IF (amount <= 0)\n", + " THROW ArgumentOutOfRangeException{ nameof(amount), \"Amount of deposit must be positive\" }\n", + " ENDIF \n", + " var deposit := Transaction{ amount, depositDate, notes }\n", + " allTransactions:Add(deposit)\n", + " END METHOD\n", + "\n", + " PUBLIC METHOD MakeWithdrawal( amount AS DECIMAL, withdrawDate AS DateTime, notes AS STRING ) AS VOID\n", + " // \n", + " IF (amount <= 0)\n", + " THROW ArgumentOutOfRangeException{ nameof(amount), \"Amount of withdrawal must be positive\" }\n", + " ENDIF\n", + " IF (Balance - amount < 0)\n", + " THROW InvalidOperationException{ \"Not sufficient funds for this withdrawal\" }\n", + " ENDIF\n", + " var withdrawal = Transaction{-amount, withdrawDate, notes}\n", + " allTransactions:Add(withdrawal)\n", + " END METHOD\n", + "END CLASS" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "dotnet_interactive": { + "language": "xsharp" + }, + "polyglot_notebook": { + "kernelName": "xsharp" + } + }, + "outputs": [], + "source": [ + "var account := BankAccount{ \"Bruce\", 1000 }\n", + "? i\"The account number [{account:Number}] belongs to {account:Owner}\"\n", + "? i\"It has been created with {account:Balance} Euros.\"\n", + "\n", + "account:MakeWithdrawal(500, DateTime.Now, \"Batcave Rent payment\")\n", + "? i\"New Balance : {account:Balance} €.\"\n", + "\n", + "account:MakeDeposit(100, DateTime.Now, \"Peter Parker paid me back\")\n", + "? i\"New Balance : {account:Balance} €.\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Review\n", + "\n", + "You did it! You've now successfully made a bank account class that has the following attributes:\n", + "- It has a 10-digit number that uniquely identifies the bank account.\n", + "- It has a string that stores the name or names of the owners.\n", + "- The balance can be retrieved.\n", + "- It accepts deposits.\n", + "- It accepts withdrawals.\n", + "- The initial balance must be positive.\n", + "- Withdrawals cannot result in a negative balance." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Playground\n", + "Now that you've created a bank account class, you can play around with it!\n", + "\n", + "Create a way to list out the list of transactions, including the time and notes. \n", + "You have a test code below, but you can change it to match your own coding style.\n", + "\n", + "In the following example, we are retrieving the List as an array of Transaction, then enumerate it to print the values.\n", + "\n", + "***Note :*** \n", + "You will find the full code in a .prg file in the current folder, and also in a Notebook called FirsStepsCode.dib. \n", + "You can use the code with a new project in MS Visual Studio, or with our XIDE. \n", + "We will use the Notebook in the next Notebooks, based on these [explanations](../ExternalCode.md)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "dotnet_interactive": { + "language": "xsharp" + }, + "polyglot_notebook": { + "kernelName": "xsharp" + } + }, + "outputs": [], + "source": [ + "// UnComment this line to load the external Notebook (Read ../ExternalCode.md first to understand what it means)\n", + "//#!import FirstStepsCode.dib\n", + "\n", + "var account := BankAccount{ \"Bruce\", 1000 }\n", + "? i\"The account number [{account:Number}] belongs to {account:Owner}\"\n", + "? i\"It has been created with {account:Balance} Euros.\"\n", + "\n", + "account:MakeWithdrawal(500, DateTime.Now, \"Batcave Rent payment\")\n", + "? i\"New Balance : {account:Balance} €.\"\n", + "\n", + "account:MakeDeposit(100, DateTime.Now, \"Peter Parker paid me back\")\n", + "? i\"New Balance : {account:Balance} €.\"\n", + "\n", + "? \"Operations list: \"\n", + "var list := account:Transactions\n", + "FOR VAR i:= 1 TO list:Length\n", + " VAR dt := string.Format(\"{0:dd/MM/yyyy}\", list[i]:Date)\n", + " var am := string.Format(\"{0,15:N2}\", list[i]:Amount)\n", + " ? dt + am + \" \" + list[i]:Notes\n", + "NEXT\n" + ] } ], "metadata": { diff --git a/FirstSteps/16-Files.dib b/FirstSteps/16-Files.dib new file mode 100644 index 0000000..3de13c2 --- /dev/null +++ b/FirstSteps/16-Files.dib @@ -0,0 +1,136 @@ +#!meta + +{"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":[],"name":"csharp"},{"aliases":[],"languageName":"X#","name":"xsharp"}]}} + +#!markdown + +# Load the *XSharp Language kernel*. + +#!csharp + +// <-= Press on the Arrow to run Me +#r "nuget:XSharpInteractive" + +#!markdown + +# Files + +For file manipulation, .NET offers several tool classes, depending on what you want to do. Most of the elements we're interested in are found in the **System.IO** namespace, so we'll need to specify at the start of our code that we're going to `use` it by indicating : +> using System.IO + +Here are some commonly used file and directory classes: + +[File](https://learn.microsoft.com/fr-fr/dotnet/api/system.io.file?view=net-7.0) provides static methods for creating, copying, deleting, moving and opening files, and can be used to create a [FileStream](https://learn.microsoft.com/fr-fr/dotnet/api/system.io.filestream?view=net-7.0) object. + +[FileInfo](https://learn.microsoft.com/fr-fr/dotnet/api/system.io.fileinfo?view=net-7.0) provides instance methods for creating, copying, deleting, moving and opening files, and can be used to create a [FileStream](https://learn.microsoft.com/fr-fr/dotnet/api/system.io.filestream?view=net-7.0) object. + +[Directory](https://learn.microsoft.com/fr-fr/dotnet/api/system.io.directory?view=net-7.0) provides static methods for creating, moving and enumerating directories and sub-directories. + +[DirectoryInfo](https://learn.microsoft.com/fr-fr/dotnet/api/system.io.directoryinfo?view=net-7.0) provides instance methods for creating, moving and enumerating directories and sub-directories. + +#!markdown + +## To Escape or not To Escape ? + +A quick note about the usage of the Escape char. + +The character **\\** in a string, can be used to make a special character or as a separator in a file path. +For example, to make a tabulation on the console, we'd write `"\t"`. + +But how do you write a **\\** ? +In general with X#, you have nothing to do. So to get write on the console "C:\Temp", you'd have : +> + ? "C:\Temp" + +And if you want to move the text using a tab ?? +You can prefix the string with the letter **e** (as Escaped), and then **\\** are interpreted as special character marker. +> + ? e"\t" + "C:\Temp" + +#!markdown + +## Path class + +This 'tool' class can be used to check whether a string contains an extension, whether the contained path is attached to the root, to transform a relative path into an absolute path, to check whether a path exists, ... + +> Here are a few examples. Feel free to modify them after you've tried (and understood!) what's going on below. +And explore the different methods offered by `Path`. + +#!xsharp + +using System.IO + +var path1 := "c:\temp\MyTest.txt" +var path2 := "c:\temp\MyTest" +var path3 := "temp" + +IF Path.HasExtension(path1) + ? i"<{path1}> has an extension." +ENDIF + +IF !Path.HasExtension(path2) + ? i"<{path2}> has no extension." +ENDIF + +IF !Path.IsPathRooted(path3) + ? i"The Path <{path3}> has no Root information." +ENDIF + +? i"The fullpath for <{path3}> is {Path.GetFullPath(path3)}." +? i"{Path.GetTempPath()} is the temporary file folder." +? i"{Path.GetTempFileName()} can be a unique temporary file path." + +#!markdown + +## Directory class + +This class lets you create and delete folders, move files from one folder to another, and enumerate (list) the files a folder contains. + +> Try out the code below. + Then specify the type of file you're looking for, as indicated in the comment. + +#!xsharp + +using System.IO + +var sourceDirectory = "C:\Temp" + +IF !Directory.Exists( sourceDirectory ) + Directory.CreateDirectory( sourceDirectory) +ENDIF + +var listOfFiles := Directory.EnumerateFiles(sourceDirectory) +// var listOfFiles := Directory.EnumerateFiles(sourceDirectory, "*.txt") +FOREACH VAR currentFile IN listOfFiles + // We extract the Filename based on the length of the Directory + var fileName := currentFile:Substring(sourceDirectory:Length + 1) + // We could have used the Path class + // var fileName := Path.GetFileName( currentFile ) + ? fileName +NEXT + +#!markdown + +## File class + +This class, as its name suggests, enables operations related to the files themselves. +So you can test whether a file exists, rename it, delete it, etc. +You can even encrypt a file! + +#!xsharp + +using System.IO + +var path := "c:\temp\MyTest.txt" +IF File.Exists(path) + var content := File.ReadAllText( path ) + ? content +ENDIF + +#!xsharp + +// We are in another Cell, we can redefine a var, but don't forget this is specific to Notebooks ! ;) +var content := "This is the new content of the file. XSharp Rocks !!!" +File.WriteAllText( path, content ) +// Try to read the content of the file using the Notepad after crypting. Then try to open it using another computer. +File.Encrypt( path ) diff --git a/FirstSteps/17-ByteStream.dib b/FirstSteps/17-ByteStream.dib new file mode 100644 index 0000000..cdaadac --- /dev/null +++ b/FirstSteps/17-ByteStream.dib @@ -0,0 +1,218 @@ +#!meta + +{"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":[],"languageName":"csharp","name":"csharp"},{"aliases":[],"languageName":"X#","name":"xsharp"}]}} + +#!markdown + +# Load the *XSharp Language kernel*. + +#!csharp + +// <-= Press on the Arrow to run Me +#r "nuget:XSharpInteractive" + +#!markdown + +# Byte stream + +A **Stream** is a view of a sequence of Bytes (`byte[]`). +In the rest of this Notebook, we'll be talking mainly about `FileStream` file streams, but as a stream is generic, the elements we'll be covering apply equally to other forms of stream, such as `NetworkStream`, `MemoryStream`, `CryptoStream`, ... + +#!markdown + +## From Byte to String + +A character-string is ... a sequence of characters ;) + +But a character doesn't necessarily fit on 1 `Byte`, it depends on the **encoding** used. +The oldest encoding is **ASCII**: it fits on 1 Byte, and is sufficient for the 26 letters of the Western alphabet, with upper and lower case, numbers and special characters. + +But to support more possibilities (Cyrillic, Kanjii, Hangul, ...), other forms of encoding have been created, such as **Unicode**, **UTF-8**, **UTF-16**, ... +In **UTF-8**, for example, a character can be encoded in 1 to 4 bytes! It is therefore **impossible** to directly convert 1 character into 1 byte. + +In C#, there are **encoders** that allow us to switch from one to the other, but this implies that you know what you're manipulating. + +> For example, you can switch from a string to an array of bytes... + +#!xsharp + +USING System.Text + +var oneString := "Hi to all ! Here are some international characters : é, ô, ü, ß" +var ascii := ASCIIEncoding{} +var utf8 := UTF8Encoding{} +LOCAL infoAscii := ascii:GetBytes(oneString) AS BYTE[] +var infoUtf8 := utf8:GetBytes(oneString) + +? i"In ASCII, we have {infoAscii:Length} bytes" +? i"In UTF8, we have {infoUtf8:Length} bytes" + +#!markdown + +> ... and from an array of bytes to a string + +To do this, you'll need to specify the **encoder**, the array containing the information, as well as the position in the array at which you're starting, and on how many bytes. +You can also simply give the byte array, and .Net will decode the whole thing. + +#!xsharp + +// Let's try decode with the wrong Decoder +var newString := ascii:GetString( infoAscii, 0, infoAscii:Length ) +? newString + +// Ok, now use the right one +newString := utf8:GetString( infoUtf8 ) +? newString + +#!markdown + +## Writing to a stream + +We're going to write bytes which, as with the previous manipulations, can be converted from strings. + +#!xsharp + +using System.IO +using System.Text + +// Our Test file +LOCAL path := "c:\temp\MyTest.txt" AS STRING +// Remove, if needed +IF File.Exists(path) + File.Delete(path) +ENDIF +// Create the file and stream object +LOCAL myStream := File.Create(path) AS FileStream + +var someText := "XSharp Rocks !! Spread the words !" +var utf8 := UTF8Encoding{} +var infoUtf8 := utf8:GetBytes(someText) +myStream:Write( infoUtf8 ) +// !!! Warning !!! Don't forget to close the Stream !!! +myStream:Close() + +#!markdown + +## Opening / Closing + +A common problem is *forgetting* to close the feed. +In such a situation, it's the **Garbage Collector** that will decide when to close, and this will be at the moment when the variable is deleted from memory, a moment we don't control. + +> Try the previous code again, but first comment out the **closure**. +The first time you run it, you won't see any change, but if you try a second time, what are the consequences? +**You will probably have to close VSCode to move on to the next step....** + +#!markdown + +## Variable lifetime: `using` statement + +The `using` instruction provides a convenient syntax that ensures the correct use of objects by specifying their lifetime. + +> The code below performs the same function as the previous one, but specifies when to _create_ and _delete_ the flow variable. +> The _deletion_ of the stream causes the stream to be closed, as the `FileStream` class is written that way. + +#!xsharp + +using System.IO +using System.Text + +// Our Test file +LOCAL path := "c:\temp\MyTest.txt" AS STRING +// Remove, if needed +IF File.Exists(path) + File.Delete(path) +ENDIF +// Create the file and stream object +BEGIN USING VAR myStream := File.Create(path) + + var someText := "XSharp Rocks !! Spread the words !" + var utf8 := UTF8Encoding{} + var infoUtf8 := utf8:GetBytes(someText) + myStream:Write( infoUtf8 ) + +END USING +// At this point, the variable 'myStream' does not exist anymore, and the stream has been closed. + +#!markdown + +# Reading a feed + +And now, how to read a stream? + +First, we'll open it by specifying not only its path but also its opening mode, via the [FileMode](https://learn.microsoft.com/fr-fr/dotnet/api/system.io.filemode?view=net-7.0) enumeration. + +#!xsharp + +using System.IO +using System.Text + +LOCAL myStream := File.Open(path, FileMode.Open ) AS FileStream + +#!markdown + +We can now retrieve the stream size, reserving an array of bytes to store all the bytes. + +#!xsharp + +var size := myStream:Length +var content := BYTE[]{size} +myStream:Read( content, 0, (int)size ) +? i"{size} Bytes read." + +#!markdown + +And don't forget to close the stream... + +#!xsharp + +myStream:Close() + +#!markdown + +Note that as you read the byte stream, you move forward in the file. +The **file pointer** remembers where you are in the file, and you can advance it as you wish using `Seek`. + +> In this way, we position ourselves on the last 5 bytes of files, before reading them and displaying their value in Hexadecimal, Decimal and Character. +> Note the content of the format string: `{index[,alignment][:formatString]}`. + +#!xsharp + +LOCAL myStream := File.Open(path, FileMode.Open ) AS FileStream + +var size := myStream:Length +IF size > 6 + // Move the file pointer + myStream:Seek( size - 6, SeekOrigin.Begin ) +ENDIF + +var content := BYTE[]{6} +// Read from the current position +myStream:Read( content, 0, content:Length ) +myStream:Close() + +foreach VAR oneByte in content + ? string.Format("0x{0:X2}", oneByte) + " - " + string.Format("{0,3:#}", oneByte) + " - " + string.Format("{0}", Convert.ToChar(oneByte) ) +NEXT + +#!markdown + +And you can also read the entire contents of the file at once. + +#!xsharp + +var content := File.ReadAllBytes( path ) +? i"{content:Length} Bytes read." + +#!markdown + +# Sanbox: Your turn + +Make a program that can read a text file, and rewrite it backwards. +> 1. The new file will have the same name as the original, but in reverse (including the extension). +> 2. The contents of the new file will be the same as the original, but in reverse...character by character. +> 3. Display the contents of the new file. +> 4. Once the copy has been made, write the code to check that the files are indeed “symmetrical”. + +#!xsharp + +? "Sandbox" diff --git a/FirstSteps/18-TextStream.dib b/FirstSteps/18-TextStream.dib new file mode 100644 index 0000000..57a655a --- /dev/null +++ b/FirstSteps/18-TextStream.dib @@ -0,0 +1,106 @@ +#!meta + +{"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":[],"languageName":"csharp","name":"csharp"},{"aliases":[],"languageName":"X#","name":"xsharp"}]}} + +#!markdown + +# Load the *XSharp Language kernel*. + +#!csharp + +// <-= Press on the Arrow to run Me +#r "nuget:XSharpInteractive" + +#!markdown + +# Text Stream + +To manage the content of a stream in text form, we can use `tool` classes, which will exploit the byte stream for us. + +The `StreamWriter` class lets you write text directly into a stream. +The `StreamReader` class lets you read text from a stream. + + +These can be used in a number of ways. + +## Direct access + +> In the example below, by specifying a file name, the stream will be created by the `StreamWriter` class. + + +#!xsharp + +using System.IO + +var sourceDirectory := "C:\Temp" + +IF !Directory.Exists( sourceDirectory ) + Directory.CreateDirectory( sourceDirectory) +ENDIF + +var listOfFiles := Directory.EnumerateFiles(sourceDirectory); +// var listOfFiles := Directory.EnumerateFiles(sourceDirectory, "*.txt"); +// Write the list of file, on your Desktop +VAR desktop := Environment.GetFolderPath(Environment.SpecialFolder.Desktop) +? i"Desktop path : {desktop}" +var file := Path.Combine( desktop, "listOfFiles.txt" ) +? i"Saving in : {file}" +BEGIN USING VAR sw := StreamWriter{file} + FOREACH VAR currentFile IN listOfFiles + // Extract Filename + var fileName := Path.GetFileName( currentFile ) + sw:WriteLine( fileName ) + NEXT +END USING + +#!markdown + +## Access via the Stream + +> In the example below, we use the stream. +Here it's a file stream, but it could be another type of stream. (Network, ...) + +#!xsharp + +using System.IO + +var sourceDirectory := "C:\Temp" + +IF !Directory.Exists( sourceDirectory ) + Directory.CreateDirectory( sourceDirectory) +ENDIF + +var listOfFiles := Directory.EnumerateFiles(sourceDirectory); +// var listOfFiles := Directory.EnumerateFiles(sourceDirectory, "*.txt"); +// Write the list of file, on your Desktop +VAR desktop := Environment.GetFolderPath(Environment.SpecialFolder.Desktop) +? i"Desktop path : {desktop}" +var file := Path.Combine( desktop, "listOfFiles.txt" ) +? i"Saving in : {file}" +// Create a File Stream +BEGIN USING VAR fs := FileStream{file, FileMode.OpenOrCreate} + // Use this Stream to Write + BEGIN USING VAR sw := StreamWriter{fs} + FOREACH VAR currentFile IN listOfFiles + // Extract Filename + var fileName := Path.GetFileName( currentFile ) + sw:WriteLine( fileName ) + NEXT + END USING +END USING + +#!markdown + +# Sandbox: Your turn + +Make a program that can read a text file, and rewrite it backwards, **line by line**. +You'll need to use the `StreamWriter` class and the `StreamReader` class. + +> 1. The new file will have the same name as the original, but in reverse (including the extension). +> 2. The contents of the new file will be the same as the original, but in reverse... line by line. +> 3. Display the contents of the new file. +> 4. Once the copy has been made, write the code to check that the files are indeed “symmetrical”. + +#!xsharp + +? "Sandbox" diff --git a/FirstSteps/19-BankAccount_and_CSVFile.dib b/FirstSteps/19-BankAccount_and_CSVFile.dib new file mode 100644 index 0000000..079c305 --- /dev/null +++ b/FirstSteps/19-BankAccount_and_CSVFile.dib @@ -0,0 +1,159 @@ +#!meta + +{"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":[],"languageName":"csharp","name":"csharp"},{"aliases":[],"languageName":"X#","name":"xsharp"}]}} + +#!markdown + +# Load the *XSharp Language kernel*. + +#!csharp + +// <-= Press on the Arrow to run Me +#r "nuget:XSharpInteractive" + +#!markdown + +# Backup and Restore + +If you have followed the previous Notebooks, you have created a simple BankAccount management application and you know to read and write files. + +The goal of this Notebook is to save and restore your BankAccount informations. +To achieve, we will create a `StorageClass` that will do the job. + +## Storage File + +### CSV Backup Method + +This method will create the file, based on the Owner's name and enumerate all transactions to write them as a **C**omma **S**eparated **V**alue. + +> Add this code to the `Backup` method : + + var fileName := account:Owner + ".csv" + BEGIN USING VAR sw := StreamWriter{fileName} + VAR allTransact := account:Transactions + FOREACH VAR transact IN allTransact + // + var info := string.Format("{0:dd/MM/yyyy}", transact:Date) + info += ";" + string.Format("{0:N2}", transact:Amount) + info += ";" + transact:Notes + sw:WriteLine( info ) + NEXT + END USING + +#!xsharp + +// Import the existing Class definition +#!import FirstStepsCode.dib +using System.IO + +CLASS StorageFile + + METHOD CSVBackup( account AS BankAccount ) AS VOID + // Paste Here + END METHOD + +END CLASS + +#!markdown + +Now, we must create a BankAccount object with some transactions, then we can create a StorageFile and backup the . + +#!xsharp + +var account := BankAccount{ "Bruce", 1000 } +account:MakeWithdrawal(500, DateTime.Now, "Batcave Rent payment") +account:MakeDeposit(100, DateTime.Now, "Peter Parker paid me back") + +var storage := StorageFile{} +storage:CSVBackup( account ) + +#!markdown + +You should have a file called **Bruce.csv** in your folder : Open it, and check the content against the previous code. + +#!markdown + +### CSV Restore Method + +This method will read a file, and create a BankAccount object using the FileName as Owner's name. We will read line by line and create the Transactions. + +In order to proceed, we are using the `Split` method of the `String` class : +It search for a char, copy the parts between these char to a string array. +Then we will **Convert** each part to expected type in order to create a **Transaction**, using the methods of the **Convert** class. + +`DateTime` are a bit more complex to handle due to the various `CultureInfo`. +Here, we will specify the format of the string used in the convertion. + + +> Add this code to the `Restore` method : + + LOCAL account := NULL AS BankAccount + BEGIN USING VAR sr := StreamReader{fileName} + // + LOCAL oneLine AS STRING + REPEAT + oneLine := sr:ReadLine() + IF !String.IsNullOrEmpty( oneLine ) + VAR info := oneLine:Split( ';' ) + var dt := DateTime.ParseExact(info[1], "dd/MM/yyyy", CultureInfo.InvariantCulture) + var amount := Convert.ToDecimal( info[2] ) + IF account == NULL + account := BankAccount{ Path.GetFileNameWithoutExtension( fileName ), amount } + ELSE + IF amount >= 0 + account:MakeDeposit( amount, dt, info[3] ) + ELSE + account:MakeWithdrawal( -amount, dt, info[3] ) + ENDIF + ENDIF + ENDIF + UNTIL ( String.IsNullOrEmpty( oneLine ) ) + END USING + RETURN account + +#!xsharp + +// Import the existing Class definition +#!import FirstStepsCode.dib +using System.IO +using System.Globalization + +CLASS StorageFile + + METHOD CSVBackup( account AS BankAccount ) AS VOID + var fileName := account:Owner + ".csv" + BEGIN USING VAR sw := StreamWriter{fileName} + VAR allTransact := account:Transactions + FOREACH VAR transact IN allTransact + // + var info := string.Format("{0:dd/MM/yyyy}", transact:Date) + info += ";" + string.Format("{0:N2}", transact:Amount) + info += ";" + transact:Notes + sw:WriteLine( info ) + NEXT + END USING + END METHOD + + METHOD CSVRestore( fileName AS STRING ) AS BankAccount + // Paste here + + + END METHOD + +END CLASS + +#!markdown + +Ok, we have a Restore Method : Let's try it using the previously created file ! + +#!xsharp + +var storage := StorageFile{} +var account := storage:CSVRestore( "Bruce.csv" ) +? "Operations list: " +var list := account:Transactions +FOR VAR i:= 1 TO list:Length + VAR dt := string.Format("{0:dd/MM/yyyy}", list[i]:Date) + var am := string.Format("{0,15:N2}", list[i]:Amount) + ? dt + am + " " + list[i]:Notes +NEXT diff --git a/FirstSteps/FirstStepsCode.dib b/FirstSteps/FirstStepsCode.dib new file mode 100644 index 0000000..1a04b84 --- /dev/null +++ b/FirstSteps/FirstStepsCode.dib @@ -0,0 +1,83 @@ +#!meta + +{"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":[],"name":"csharp"},{"aliases":[],"languageName":"X#","name":"xsharp"}]}} + +#!csharp + +// Load the XSharp engine +#r "nuget:XSharpInteractive" + +#!xsharp + +using System.Collections.Generic + +CLASS Transaction + // Properties + PUBLIC PROPERTY Amount AS DECIMAL AUTO GET + PUBLIC PROPERTY Date AS DateTime AUTO GET + PUBLIC PROPERTY Notes AS STRING AUTO GET + + // Constructor + PUBLIC CONSTRUCTOR( trAmount AS Decimal, trDate AS DateTime, trNote AS String ) + SELF:Amount := trAmount + SELF:Date := trDate + SELF:Notes := trNote + END CONSTRUCTOR + +END CLASS + + +CLASS BankAccount + // Properties + PUBLIC PROPERTY Number AS STRING AUTO GET SET + PUBLIC PROPERTY Owner AS STRING AUTO GET SET + PUBLIC PROPERTY Balance AS Decimal + GET + VAR currentBalance := 0.0M + FOREACH VAR item IN SELF:allTransactions + currentBalance += item:Amount + NEXT + RETURN currentBalance + END GET + END PROPERTY + PRIVATE STATIC accountBaseNumber := 1234567890 AS INT + PRIVATE allTransactions := List{} AS List + + // Constructor + PUBLIC CONSTRUCTOR( name AS STRING, initialBalance AS DECIMAL ) + SELF:Owner := name + SELF:Number := accountBaseNumber:ToString() + accountBaseNumber ++ + // + SELF:MakeDeposit(initialBalance, DateTime.Now, "Initial balance") + END CONSTRUCTOR + + // Methods + PUBLIC METHOD MakeDeposit( amount AS DECIMAL, depositDate AS DateTime, notes AS STRING ) AS VOID + // + IF (amount <= 0) + THROW ArgumentOutOfRangeException{ nameof(amount), "Amount of deposit must be positive" } + ENDIF + var deposit := Transaction{ amount, depositDate, notes } + allTransactions:Add(deposit) + END METHOD + + PUBLIC METHOD MakeWithdrawal( amount AS DECIMAL, withdrawDate AS DateTime, notes AS STRING ) AS VOID + // + IF (amount <= 0) + THROW ArgumentOutOfRangeException{ nameof(amount), "Amount of withdrawal must be positive" } + ENDIF + IF (Balance - amount < 0) + THROW InvalidOperationException{ "Not sufficient funds for this withdrawal" } + ENDIF + var withdrawal = Transaction{-amount, withdrawDate, notes} + allTransactions:Add(withdrawal) + END METHOD + + // Retrieve a copy of the List + PROPERTY Transactions AS Transaction[] + GET + RETURN allTransactions:ToArray() + END GET + END PROPERTY +END CLASS diff --git a/FirstSteps/FirstStepsCode.prg b/FirstSteps/FirstStepsCode.prg new file mode 100644 index 0000000..61d0d50 --- /dev/null +++ b/FirstSteps/FirstStepsCode.prg @@ -0,0 +1,73 @@ +using System.Collections.Generic + +CLASS Transaction + // Properties + PUBLIC PROPERTY Amount AS DECIMAL AUTO GET + PUBLIC PROPERTY Date AS DateTime AUTO GET + PUBLIC PROPERTY Notes AS STRING AUTO GET + + // Constructor + PUBLIC CONSTRUCTOR( trAmount AS Decimal, trDate AS DateTime, trNote AS String ) + SELF:Amount := trAmount + SELF:Date := trDate + SELF:Notes := trNote + END CONSTRUCTOR + +END CLASS + + +CLASS BankAccount + // Properties + PUBLIC PROPERTY Number AS STRING AUTO GET SET + PUBLIC PROPERTY Owner AS STRING AUTO GET SET + PUBLIC PROPERTY Balance AS Decimal + GET + VAR currentBalance := 0.0M + FOREACH VAR item IN SELF:allTransactions + currentBalance += item:Amount + NEXT + RETURN currentBalance + END GET + END PROPERTY + PRIVATE STATIC accountBaseNumber := 1234567890 AS INT + PRIVATE allTransactions := List{} AS List + + // Constructor + PUBLIC CONSTRUCTOR( name AS STRING, initialBalance AS DECIMAL ) + SELF:Owner := name + SELF:Number := accountBaseNumber:ToString() + accountBaseNumber ++ + // + SELF:MakeDeposit(initialBalance, DateTime.Now, "Initial balance") + END CONSTRUCTOR + + // Methods + PUBLIC METHOD MakeDeposit( amount AS DECIMAL, depositDate AS DateTime, notes AS STRING ) AS VOID + // + IF (amount <= 0) + THROW ArgumentOutOfRangeException{ nameof(amount), "Amount of deposit must be positive" } + ENDIF + var deposit := Transaction{ amount, depositDate, notes } + allTransactions:Add(deposit) + END METHOD + + PUBLIC METHOD MakeWithdrawal( amount AS DECIMAL, withdrawDate AS DateTime, notes AS STRING ) AS VOID + // + IF (amount <= 0) + THROW ArgumentOutOfRangeException{ nameof(amount), "Amount of withdrawal must be positive" } + ENDIF + IF (Balance - amount < 0) + THROW InvalidOperationException{ "Not sufficient funds for this withdrawal" } + ENDIF + var withdrawal = Transaction{-amount, withdrawDate, notes} + allTransactions:Add(withdrawal) + END METHOD + + // Retrieve a copy of the List + PROPERTY Transactions AS Transaction[] + GET + RETURN allTransactions:ToArray() + END GET + END PROPERTY +END CLASS +