From 7e21378d41778a59c649255a1fdbd62460f17d01 Mon Sep 17 00:00:00 2001 From: Nathan Summers Date: Thu, 20 Jun 2024 12:19:33 -0400 Subject: [PATCH 1/2] Initial support for C# This is enough to make Hello World work, modulo a few issues that can't be solved with a straight port. --- inform7/Internal/Miscellany/inform7_cslib.cs | 2254 +++++++++++++++++ inter/final-module/Chapter 1/Final Module.w | 10 + .../final-module/Chapter 2/Code Generators.w | 3 +- inter/final-module/Chapter 6/C# Arithmetic.w | 324 +++ inter/final-module/Chapter 6/C# Assembly.w | 757 ++++++ inter/final-module/Chapter 6/C# Conditions.w | 143 ++ .../Chapter 6/C# Function Model.w | 685 +++++ .../Chapter 6/C# Global Variables.w | 151 ++ .../Chapter 6/C# Input-Output Model.w | 354 +++ inter/final-module/Chapter 6/C# Literals.w | 283 +++ .../final-module/Chapter 6/C# Memory Model.w | 541 ++++ inter/final-module/Chapter 6/C# Miniglk.w | 879 +++++++ inter/final-module/Chapter 6/C# Namespace.w | 152 ++ .../final-module/Chapter 6/C# Object Model.w | 1114 ++++++++ .../Chapter 6/C# Program Control.w | 186 ++ inter/final-module/Chapter 6/C# References.w | 118 + .../Chapter 6/C# Utility Functions.w | 131 + inter/final-module/Chapter 6/Final C#.w | 552 ++++ inter/final-module/Contents.w | 17 + scripts/inform.mkscript | 16 +- .../Chapter 2/Target Virtual Machines.w | 6 + 21 files changed, 8671 insertions(+), 5 deletions(-) create mode 100644 inform7/Internal/Miscellany/inform7_cslib.cs create mode 100644 inter/final-module/Chapter 6/C# Arithmetic.w create mode 100644 inter/final-module/Chapter 6/C# Assembly.w create mode 100644 inter/final-module/Chapter 6/C# Conditions.w create mode 100644 inter/final-module/Chapter 6/C# Function Model.w create mode 100644 inter/final-module/Chapter 6/C# Global Variables.w create mode 100644 inter/final-module/Chapter 6/C# Input-Output Model.w create mode 100644 inter/final-module/Chapter 6/C# Literals.w create mode 100644 inter/final-module/Chapter 6/C# Memory Model.w create mode 100644 inter/final-module/Chapter 6/C# Miniglk.w create mode 100644 inter/final-module/Chapter 6/C# Namespace.w create mode 100644 inter/final-module/Chapter 6/C# Object Model.w create mode 100644 inter/final-module/Chapter 6/C# Program Control.w create mode 100644 inter/final-module/Chapter 6/C# References.w create mode 100644 inter/final-module/Chapter 6/C# Utility Functions.w create mode 100644 inter/final-module/Chapter 6/Final C#.w diff --git a/inform7/Internal/Miscellany/inform7_cslib.cs b/inform7/Internal/Miscellany/inform7_cslib.cs new file mode 100644 index 0000000000..39740ac714 --- /dev/null +++ b/inform7/Internal/Miscellany/inform7_cslib.cs @@ -0,0 +1,2254 @@ +/* This is a library of C# code to support Inter code compiled to C#. It was + generated mechanically from the Inter source code, so to change this material, + edit that and not this file. */ + +using System; +using System.IO; + +namespace Inform { +struct RngSeed { + internal uint A; + internal uint interval; + internal uint counter; + + public static RngSeed i7_initial_rng_seed() { + RngSeed seed = new RngSeed(); + seed.A = 1; + return seed; + } +} + +class State { + internal const int I7_ASM_STACK_CAPACITY = 128; + internal const int I7_TMP_STORAGE_CAPACITY = 128; + + internal byte[] memory; + internal int himem; + internal readonly int[] stack = new int[I7_ASM_STACK_CAPACITY]; + internal int stack_pointer; + internal int[] object_tree_parent; + internal int[] object_tree_child; + internal int[] object_tree_sibling; + internal int[] variables; + internal readonly int[] tmp = new int[I7_TMP_STORAGE_CAPACITY]; + internal int current_output_stream_ID; + internal RngSeed seed = RngSeed.i7_initial_rng_seed(); +} +class Snapshot { + internal bool valid; + internal State then; + + internal Snapshot() { + then = new State(); + } +} +public partial class Process { + const int I7_MAX_SNAPSHOTS = 10; + + readonly Story story; + internal State state = new State(); + Snapshot[] snapshots = new Snapshot[I7_MAX_SNAPSHOTS]; + int snapshot_pos; + public int termination_code; + public Action receiver = Defaults.i7_default_receiver; + int send_count; + public Func sender = Defaults.i7_default_sender; + public Action stylist = Defaults.i7_default_stylist; + public Func glk_implementation = Defaults.i7_default_glk; + internal MiniGlkData miniglk; + int /*TODO: bool */ use_UTF8 = 1; + + public Process(Story story) { + this.story = story; + i7_max_objects = story.i7_max_objects; + for (int i=0; i receiver, int UTF8) { + this.receiver = receiver; + use_UTF8 = UTF8; + } + public void i7_set_process_sender(Func sender) { + this.sender = sender; + } + public void i7_set_process_stylist(Action stylist) { + this.stylist = stylist; + } + public void i7_set_process_glk_implementation(Func glk_implementation) { + this.glk_implementation = glk_implementation; + } +} +class ProcessTerminationException : Exception { + public int return_code; + public ProcessTerminationException(int returnCode) => this.return_code = returnCode; +} + +partial class Story { + public abstract int i7_fn_Main(Process proc); +} + +partial class Process { + public int i7_run_process() { + try { + i7_initialise_memory_and_stack(); + i7_initialise_variables(); + i7_empty_object_tree(); + story.i7_initialiser(this); + story.i7_initialise_object_tree(this); + story.i7_fn_Main(this); + termination_code = 0; /* terminated because the program completed */ + } catch (ProcessTerminationException termination) { + termination_code = termination.return_code; /* terminated mid-stream */ + } + return termination_code; + } + + public void i7_fatal_exit() { + // Uncomment the next line to force a crash so that the stack can be inspected in a debugger + // int x = 0; Console.Write("{0:D}", 1/x); + throw new ProcessTerminationException(1); + } + + public void i7_benign_exit() { + throw new ProcessTerminationException(0); + } +} +partial class Process { + public const int i7_lvalue_SET = 1; + public const int i7_lvalue_PREDEC = 2; + public const int i7_lvalue_POSTDEC = 3; + public const int i7_lvalue_PREINC = 4; + public const int i7_lvalue_POSTINC = 5; + public const int i7_lvalue_SETBIT = 6; + public const int i7_lvalue_CLEARBIT = 7; +} +partial class Story { + protected internal int i7_no_variables; + protected internal int[] i7_initial_variable_values; +} +partial class Process { + void i7_initialise_variables() { + // TODO: use array copy method instead + state.variables = new int[story.i7_no_variables]; + for (int i=0; i= i7_static_himem)) { + Console.Write("Memory access out of range: "); Console.WriteLine(byte_position); + //TODO use native exception? + i7_fatal_exit(); + } + return (short) (data[byte_position + 1] + + 0x1000*data[byte_position + 0]); + } + + public int i7_read_word(int array_address, int array_index) { + byte[] data = state.memory; + int byte_position = array_address + 4*array_index; + if ((byte_position < 0) || (byte_position >= i7_static_himem)) { + Console.Write("Memory access out of range: "); Console.WriteLine(byte_position); + i7_fatal_exit(); + } + return (int) data[byte_position + 3] + + 0x1000*((int) data[byte_position + 2]) + + 0x10000*((int) data[byte_position + 1]) + + 0x1000000*((int) data[byte_position + 0]); + } + public static byte I7BYTE_0(int V) => (byte) ((V & 0xFF000000) >> 24); + public static byte I7BYTE_1(int V) => (byte) ((V & 0x00FF0000) >> 16); + public static byte I7BYTE_2(int V) => (byte) ((V & 0x0000FF00) >> 8); + public static byte I7BYTE_3(int V) => (byte) (V & 0x000000FF); + + void i7_write_byte(int address, byte new_val) { + state.memory[address] = new_val; + } + + void i7_write_word(int address, int array_index,int new_val) { + int byte_position = address + 4*array_index; + if ((byte_position < 0) || (byte_position >= i7_static_himem)) { + Console.Write("Memory access out of range: "); Console.WriteLine(byte_position); + i7_fatal_exit(); + } + state.memory[byte_position] = I7BYTE_0(new_val); + state.memory[byte_position+1] = I7BYTE_1(new_val); + state.memory[byte_position+2] = I7BYTE_2(new_val); + state.memory[byte_position+3] = I7BYTE_3(new_val); + } + public byte i7_change_byte(int address, byte new_val, int way) { + byte old_val = i7_read_byte(address); + byte return_val = new_val; + switch (way) { + case i7_lvalue_PREDEC: return_val = (byte)(old_val-1); new_val = (byte)(old_val-1); break; + case i7_lvalue_POSTDEC: return_val = old_val; new_val = (byte)(old_val-1); break; + case i7_lvalue_PREINC: return_val = (byte)(old_val+1); new_val = (byte)(old_val+1); break; + case i7_lvalue_POSTINC: return_val = old_val; new_val = (byte)(old_val+1); break; + case i7_lvalue_SETBIT: new_val = (byte)(old_val | new_val); return_val = new_val; break; + case i7_lvalue_CLEARBIT: new_val = (byte)(old_val &(~new_val)); return_val = new_val; break; + } + i7_write_byte(address, new_val); + return return_val; + } + + public int i7_change_word(int array_address, int array_index, + int new_val, int way) { + byte[] data = state.memory; + int old_val = i7_read_word(array_address, array_index); + int return_val = new_val; + switch (way) { + case i7_lvalue_PREDEC: return_val = old_val-1; new_val = old_val-1; break; + case i7_lvalue_POSTDEC: return_val = old_val; new_val = old_val-1; break; + case i7_lvalue_PREINC: return_val = old_val+1; new_val = old_val+1; break; + case i7_lvalue_POSTINC: return_val = old_val; new_val = old_val+1; break; + case i7_lvalue_SETBIT: new_val = old_val | new_val; return_val = new_val; break; + case i7_lvalue_CLEARBIT: new_val = old_val &(~new_val); return_val = new_val; break; + } + i7_write_word(array_address, array_index, new_val); + return return_val; + } + internal void i7_debug_stack(string N) { + #if I7_LOG_STACK_STATE + Console.WriteLine("Called {0}: stack {1} ", N, state.stack_pointer); + for (int i=0; i ", state.stack[i]); + Console.WriteLine(); + #endif + } + + internal int i7_pull() { + if (state.stack_pointer <= 0) { + Console.WriteLine("Stack underflow"); + i7_fatal_exit(); + } + return state.stack[--(state.stack_pointer)]; + } + + internal void i7_push(int x) { + if (state.stack_pointer >= State.I7_ASM_STACK_CAPACITY) { + Console.WriteLine("Stack overflow"); + i7_fatal_exit(); + } + state.stack[state.stack_pointer++] = x; + } + void i7_copy_state(State to, State from) { + //TODO move these to State + to.himem = from.himem; + to.memory = new byte[i7_static_himem]; + //TODO copyarray + for (int i=0; i= 0) && (y < 32)) value = (x << y); + z = value; + } + + internal void i7_opcode_ushiftr(int x, int y, out int z) { + int value = 0; + if ((y >= 0) && (y < 32)) value = (x >> y); + z = value; + } + internal int i7_opcode_jeq(int x, int y) { + if (x == y) return 1; + return 0; + } + + internal int i7_opcode_jleu(int x, int y) { + uint ux, uy; + ux = unchecked((uint) x); uy = unchecked((uint) y); + if (ux <= uy) return 1; + return 0; + } + + internal int i7_opcode_jnz(int x) { + if (x != 0) return 1; + return 0; + } + + internal int i7_opcode_jz(int x) { + if (x == 0) return 1; + return 0; + } + internal void i7_opcode_nop() { + } + + internal void i7_opcode_quit() { + i7_fatal_exit(); + } + + internal void i7_opcode_verify(out int z) { + z = 0; + } + internal void i7_opcode_restoreundo(out int x) { + if (i7_has_snapshot()) { + i7_restore_snapshot(); + x = 0; + #if i7_mgl_DealWithUndo + i7_fn_DealWithUndo(this); + #endif + } else { + x = 1; + } + } + + internal void i7_opcode_saveundo(out int x) { + i7_save_snapshot(); + x = 0; + } + + internal void i7_opcode_hasundo(out int x) { + int rv = 0; if (i7_has_snapshot()) rv = 1; + x = rv; + } + + internal void i7_opcode_discardundo() { + i7_destroy_latest_snapshot(); + } + internal void i7_opcode_restart() { + Console.WriteLine("(RESTART is not implemented on this C# program.)"); + } + + internal void i7_opcode_restore(int x, out int y) { + Console.WriteLine("(RESTORE is not implemented on this C# program.)"); + y = 1; + } + + internal void i7_opcode_save(int x, out int y) { + Console.WriteLine("(SAVE is not implemented on this C# program.)"); + y = 1; + } + internal void i7_opcode_streamnum(int x) { + i7_print_decimal(x); + } + + internal void i7_opcode_streamchar(int x) { + i7_print_char(x); + } + + internal void i7_opcode_streamunichar(int x) { + i7_print_char(x); + } + const int serop_KeyIndirect = 1; + const int serop_ZeroKeyTerminates = 2; + const int serop_ReturnIndex = 4; + + internal void i7_opcode_binarysearch(int key, int keysize, + int start, int structsize, int numstructs, int keyoffset, + int options, out int s1) { + + /* If the key size is 4 or fewer, copy it directly into the keybuf array */ + byte[] keybuf = new byte[4]; + if ((options & serop_KeyIndirect) != 0) { + if (keysize <= 4) + for (int ix=0; ix byte2) cmp = 1; + } + } else { + for (int ix=0; (cmp == 0) && ix byte2) cmp = 1; + } + } + + if (cmp == 0) { + /* Success! */ + if ((options & serop_ReturnIndex) != 0) s1 = val; else s1 = addr; + return; + } + + if (cmp < 0) bot = val+1; /* Chop search range to the second half */ + else top = val; /* Chop search range to the first half */ + } + + /* Failure! */ + if ((options & serop_ReturnIndex) != 0) s1 = -1; else s1 = 0; + } + internal void i7_opcode_mcopy(int x, int y, int z) { + if (z < y) + for (int i=0; i=0; i--) + i7_write_byte(z+i, i7_read_byte(y+i)); + } + + internal void i7_opcode_mzero(int x, int y) { + for (int i=0; i> 16) & 0x7fff; + } + uint value; + if (x == 0) value = rawvalue; + else if (x >= 1) value = rawvalue % (uint) (x); + else value = (uint) -(rawvalue % (uint) (-x)); + y = (int) value; + } + + internal void i7_opcode_setrandom(int s) { + if (s == 0) { + state.seed.A = (uint) DateTime.Now.Ticks; + state.seed.interval = 0; + } else if (s < 1000) { + state.seed.interval = (uint) s; + state.seed.counter = 0; + } else { + state.seed.A = (uint) s; + state.seed.interval = 0; + } + } + + internal int i7_random(int x) { + int r; + i7_opcode_random(x, out r); + return r+1; + } + internal void i7_opcode_setiosys(int x, int y) { + } + internal void i7_opcode_gestalt(int x, int y, out int z) { + int r = 0; + switch (x) { + case 0: r = 0x00030103; break; /* Say that the Glulx version is v3.1.3 */ + case 1: r = 1; break; /* Say that the interpreter version is 1 */ + case 2: r = 0; break; /* We do not (yet) support @setmemsize */ + case 3: r = 1; break; /* We do support UNDO */ + case 4: if (y == 2) r = 1; /* We do support Glk */ + else r = 0; /* But not any other I/O system */ + break; + case 5: r = 1; break; /* We do support Unicode operations */ + case 6: r = 1; break; /* We do support @mzero and @mcopy */ + case 7: r = 0; break; /* We do not (yet) support @malloc or @mfree */ + case 8: r = 0; break; /* Since we do not support @malloc */ + case 9: r = 0; break; /* We do not support @accelfunc pr @accelparam */ + case 10: r = 0; break; /* And therefore provide none of their accelerants */ + case 11: r = 1; break; /* We do support floating-point maths operations */ + case 12: r = 1; break; /* We do support @hasundo and @discardundo */ + } + z = r; + } +} +partial class Process { + internal void i7_opcode_add(int x, int y, out int z) { + z = x + y; + } + internal void i7_opcode_sub(int x, int y, out int z) { + z = x - y; + } + internal void i7_opcode_neg(int x, out int y) { + y = -x; + } + internal void i7_opcode_mul(int x, int y, out int z) { + z = x * y; + } + + internal void i7_opcode_div(int x, int y, out int z) { + if (y == 0) { Console.WriteLine("Division of {0:D} by 0", x); i7_fatal_exit(); z=0; return; } + int result, ax, ay; + if (x < 0) { + ax = (-x); + if (y < 0) { + ay = (-y); + result = ax / ay; + } else { + ay = y; + result = -(ax / ay); + } + } else { + ax = x; + if (y < 0) { + ay = (-y); + result = -(ax / ay); + } else { + ay = y; + result = ax / ay; + } + } + z = result; + } + + internal void i7_opcode_mod(int x, int y, out int z) { + if (y == 0) { Console.WriteLine("Division of {0:D} by 0", x); z=0; i7_fatal_exit(); return; } + int result, ax, ay = (y < 0)?(-y):y; + if (x < 0) { + ax = (-x); + result = -(ax % ay); + } else { + ax = x; + result = ax % ay; + } + z = result; + } + + internal int i7_div(int x, int y) { + int z; + i7_opcode_div(x, y, out z); + return z; + } + + internal int i7_mod(int x, int y) { + int z; + i7_opcode_mod(x, y, out z); + return z; + } + internal void i7_opcode_fadd(int x, int y, out int z) { + z = i7_encode_float(i7_decode_float(x) + i7_decode_float(y)); + } + internal void i7_opcode_fsub(int x, int y, out int z) { + z = i7_encode_float(i7_decode_float(x) - i7_decode_float(y)); + } + internal void i7_opcode_fmul(int x, int y, out int z) { + z = i7_encode_float(i7_decode_float(x) * i7_decode_float(y)); + } + internal void i7_opcode_fdiv(int x, int y, out int z) { + z = i7_encode_float(i7_decode_float(x) / i7_decode_float(y)); + } + internal void i7_opcode_fmod(int x, int y, out int z, out int w) { + float fx = i7_decode_float(x), fy = i7_decode_float(y); + float fquot = fx % fy; + int quot = i7_encode_float(fquot); + int rem = i7_encode_float((fx-fquot) / fy); + if (rem == 0x0 || rem == unchecked((int)0x80000000)) { + /* When the quotient is zero, the sign has been lost in the + shuffle. We'll set that by hand, based on the original arguments. */ + rem = (x ^ y) & unchecked((int)0x80000000); + } + z = quot; + w = rem; + } + + internal void i7_opcode_floor(int x, out int y) { + y = i7_encode_float((float)Math.Floor(i7_decode_float(x))); + } + internal void i7_opcode_ceil(int x, out int y) { + y = i7_encode_float((float)Math.Ceiling(i7_decode_float(x))); + } + + internal void i7_opcode_ftonumn(int x, out int y) { + float fx = i7_decode_float(x); + int result; + if (Math.Sign(fx) > 1) { + if (float.IsNaN(fx) || float.IsInfinity(fx) || (fx > 2147483647.0)) + result = 0x7FFFFFFF; + else + result = (int) (Math.Round(fx)); + } + else { + if (float.IsNaN(fx) || float.IsInfinity(fx) || (fx < -2147483647.0)) + result = unchecked((int)0x80000000); + else + result = (int) (Math.Round(fx)); + } + y = result; + } + + internal void i7_opcode_ftonumz(int x, out int y) { + float fx = i7_decode_float(x); + int result; + if (Math.Sign(fx) > 1) { + if (float.IsNaN(fx) || float.IsInfinity(fx) || (fx > 2147483647.0)) + result = 0x7FFFFFFF; + else + result = (int) (Math.Truncate(fx)); + } + else { + if (float.IsNaN(fx) || float.IsInfinity(fx) || (fx < -2147483647.0)) + result = unchecked((int)0x80000000); + else + result = (int) (Math.Truncate(fx)); + } + y = result; + } + + internal void i7_opcode_numtof(int x, out int y) { + y = i7_encode_float((float) x); + } + internal void i7_opcode_exp(int x, out int y) { + y = i7_encode_float((float)Math.Exp(i7_decode_float(x))); + } + internal void i7_opcode_log(int x, out int y) { + y = i7_encode_float((float)Math.Log(i7_decode_float(x))); + } + internal void i7_opcode_pow(int x, int y, out int z) { + if (i7_decode_float(x) == 1.0f) + z = i7_encode_float(1.0f); + else if ((i7_decode_float(y) == 0.0f) || (i7_decode_float(y) == -0.0f)) + z = i7_encode_float(1.0f); + else if ((i7_decode_float(x) == -1.0f) && float.IsInfinity(i7_decode_float(y))) + z = i7_encode_float(1.0f); + else + z = i7_encode_float((float)Math.Pow(i7_decode_float(x), i7_decode_float(y))); + } + internal void i7_opcode_sqrt(int x, out int y) { + y = i7_encode_float((float)Math.Sqrt(i7_decode_float(x))); + } + internal void i7_opcode_sin(int x, out int y) { + y = i7_encode_float((float)Math.Sin(i7_decode_float(x))); + } + internal void i7_opcode_cos(int x, out int y) { + y = i7_encode_float((float)Math.Cos(i7_decode_float(x))); + } + internal void i7_opcode_tan(int x, out int y) { + y = i7_encode_float((float)Math.Tan(i7_decode_float(x))); + } + + internal void i7_opcode_asin(int x, out int y) { + y = i7_encode_float((float)Math.Asin(i7_decode_float(x))); + } + internal void i7_opcode_acos(int x, out int y) { + y = i7_encode_float((float)Math.Acos(i7_decode_float(x))); + } + internal void i7_opcode_atan(int x, out int y) { + y = i7_encode_float((float)Math.Atan(i7_decode_float(x))); + } + + internal int i7_opcode_jfeq(int x, int y, int z) { + int result; + if ((z & 0x7F800000) == 0x7F800000 && (z & 0x007FFFFF) != 0) { + /* The delta is NaN, which can never match. */ + result = 0; + } else if ((x == 0x7F800000 || (uint) x == 0xFF800000) + && (y == 0x7F800000 || (uint) y == 0xFF800000)) { + /* Both are infinite. Opposite infinities are never equal, + even if the difference is infinite, so this is easy. */ + result = (x == y) ? 1 : 0; + } else { + float fx = i7_decode_float(y) - i7_decode_float(x); + float fy = System.Math.Abs(i7_decode_float(z)); + result = (fx <= fy && fx >= -fy) ? 1 : 0; + } + return result; + } + + internal int i7_opcode_jfne(int x, int y, int z) { + int result; + if ((z & 0x7F800000) == 0x7F800000 && (z & 0x007FFFFF) != 0) { + /* The delta is NaN, which can never match. */ + result = 0; + } else if ((x == 0x7F800000 || (uint) x == 0xFF800000) + && (y == 0x7F800000 || (uint) y == 0xFF800000)) { + /* Both are infinite. Opposite infinities are never equal, + even if the difference is infinite, so this is easy. */ + result = (x == y) ? 1 : 0; + } else { + float fx = i7_decode_float(y) - i7_decode_float(x); + float fy = System.Math.Abs(i7_decode_float(z)); + result = (fx <= fy && fx >= -fy) ? 1 : 0; + } + return result; + } + + internal int i7_opcode_jfge(int x, int y) { + if (i7_decode_float(x) >= i7_decode_float(y)) return 1; + return 0; + } + + internal int i7_opcode_jflt(int x, int y) { + if (i7_decode_float(x) < i7_decode_float(y)) return 1; + return 0; + } + + internal int i7_opcode_jisinf(int x) { + if (x == 0x7F800000 || (uint) x == 0xFF800000) return 1; + return 0; + } + + internal int i7_opcode_jisnan(int x) { + if ((x & 0x7F800000) == 0x7F800000 && (x & 0x007FFFFF) != 0) return 1; + return 0; + } +} +partial class Story { + internal int i7_strings_base; +} + +partial class Process { + string[] i7_texts; + public string i7_text_to_CLR_string(int str) { + return i7_texts[str - story.i7_strings_base]; + } +} +partial class Process { + public void i7_print_dword(int at) { + for (byte i=1; i<=i7_mgl_DICT_WORD_SIZE; i++) { + int c = i7_read_word(at, i); + if (c == 0) break; + i7_print_char(c); + } + } +} +partial class Story { + protected internal int i7_max_objects; + protected internal int i7_no_property_ids; + protected internal int i7_functions_base; + protected internal int[] i7_metaclass_of; + protected internal int[] i7_class_of; + protected internal readonly int i7_special_class_Routine; + protected internal readonly int i7_special_class_String; + protected internal readonly int i7_special_class_Class; + protected internal readonly int i7_special_class_Object; + protected internal int i7_metaclass(int id) { + if (id <= 0) return 0; + if (id >= i7_functions_base) return i7_special_class_Routine; + if (id >= i7_strings_base) return i7_special_class_String; + return i7_metaclass_of[id]; + } +} +partial class Process { + internal int i7_ofclass(int id, int cl_id) { + if ((id <= 0) || (cl_id <= 0)) return 0; + if (id >= story.i7_functions_base) { + if (cl_id == story.i7_special_class_Routine) return 1; + return 0; + } + if (id >= story.i7_strings_base) { + if (cl_id == story.i7_special_class_String) return 1; + return 0; + } + if (id == story.i7_special_class_Class) { + if (cl_id == story.i7_special_class_Class) return 1; + return 0; + } + if (cl_id == story.i7_special_class_Object) { + if (story.i7_metaclass_of[id] == story.i7_special_class_Object) return 1; + return 0; + } + int cl_found = story.i7_class_of[id]; + while (cl_found != story.i7_special_class_Class) { + if (cl_id == cl_found) return 1; + cl_found = story.i7_class_of[cl_found]; + } + return 0; + } + int i7_max_objects; + int i7_no_property_ids; + void i7_empty_object_tree() { + //TODO: move to State? + i7_max_objects = story.i7_max_objects; + i7_no_property_ids = story.i7_no_property_ids; + state.object_tree_parent = new int[i7_max_objects]; + state.object_tree_child = new int[i7_max_objects]; + state.object_tree_sibling = new int[i7_max_objects]; + for (int i=0; i= i7_max_objects) || + (pr < 0) || (pr >= i7_no_property_ids)) return 0; + return 4*i7_properties[(int) obj].len[(int) pr]; + } + + internal int i7_prop_addr(int K, int obj, int pr_array) { + int pr = i7_read_word(pr_array, 1); + if ((obj <= 0) || (obj >= i7_max_objects) || + (pr < 0) || (pr >= i7_no_property_ids)) return 0; + return i7_properties[(int) obj].address[(int) pr]; + } + + internal bool i7_provides(int owner_id, int pr_array) { + int prop_id = i7_read_word(pr_array, 1); + if ((owner_id <= 0) || (owner_id >= i7_max_objects) || + (prop_id < 0) || (prop_id >= i7_no_property_ids)) return false; + while (owner_id != 1) { + if (i7_properties[(int) owner_id].address[(int) prop_id] != 0) return true; + owner_id = story.i7_class_of[owner_id]; + } + return false; + } + internal void i7_move(int obj, int to) { + if ((obj <= 0) || (obj >= i7_max_objects)) return; + int p = state.object_tree_parent[obj]; + if (p != 0) { + if (state.object_tree_child[p] == obj) { + state.object_tree_child[p] = state.object_tree_sibling[obj]; + } else { + int c = state.object_tree_child[p]; + while (c != 0) { + if (state.object_tree_sibling[c] == obj) { + state.object_tree_sibling[c] = state.object_tree_sibling[obj]; + break; + } + c = state.object_tree_sibling[c]; + } + } + } + state.object_tree_parent[obj] = to; + state.object_tree_sibling[obj] = 0; + if (to != 0) { + state.object_tree_sibling[obj] = state.object_tree_child[to]; + state.object_tree_child[to] = obj; + } + } + int i7_parent(int id) { + if (story.i7_metaclass( id) != story.i7_special_class_Object) return 0; + return state.object_tree_parent[id]; + } + int i7_child(int id) { + if (story.i7_metaclass( id) != story.i7_special_class_Object) return 0; + return state.object_tree_child[id]; + } + int i7_children(int id) { + if (story.i7_metaclass( id) != story.i7_special_class_Object) return 0; + int c=0; + for (int i=0; i= i7_max_objects) || + (prop_id < 0) || (prop_id >= i7_no_property_ids)) return 0; + while (i7_properties[(int) owner_id].address[(int) prop_id] == 0) { + owner_id = story.i7_class_of[owner_id]; + if (owner_id == story.i7_special_class_Class) return 0; + } + int address = i7_properties[(int)owner_id].address[(int)prop_id]; + return i7_read_word(address, 0); + } + + void i7_write_prop_value(int owner_id, int pr_array, int val) { + int prop_id = i7_read_word(pr_array, 1); + if ((owner_id <= 0) || (owner_id >= i7_max_objects) || + (prop_id < 0) || (prop_id >= i7_no_property_ids)) { + Console.WriteLine("impossible property write ({0:D}, {1:D})", owner_id, prop_id); + i7_fatal_exit(); + } + int address = i7_properties[(int) owner_id].address[(int) prop_id]; + if (address != 0) i7_write_word(address, 0, val); + else { + Console.WriteLine("impossible property write ({0:D}, {1:D})", owner_id, prop_id); + i7_fatal_exit(); + } + } + + int i7_change_prop_value(int obj, int pr, + int to, int way) { + int val = i7_read_prop_value(obj, pr), new_val = val; + switch (way) { + case i7_lvalue_SET: + i7_write_prop_value(obj, pr, to); new_val = to; break; + case i7_lvalue_PREDEC: + new_val = val-1; i7_write_prop_value(obj, pr, val-1); break; + case i7_lvalue_POSTDEC: + new_val = val; i7_write_prop_value(obj, pr, val-1); break; + case i7_lvalue_PREINC: + new_val = val+1; i7_write_prop_value(obj, pr, val+1); break; + case i7_lvalue_POSTINC: + new_val = val; i7_write_prop_value(obj, pr, val+1); break; + case i7_lvalue_SETBIT: + new_val = val | new_val; i7_write_prop_value(obj, pr, new_val); break; + case i7_lvalue_CLEARBIT: + new_val = val &(~new_val); i7_write_prop_value(obj, pr, new_val); break; + } + return new_val; + } +} +partial class Process { + internal bool i7_provides_gprop_inner(int K, int obj, int pr, + int i7_mgl_OBJECT_TY, int i7_mgl_value_ranges, + int i7_mgl_value_property_holders, int i7_mgl_COL_HSIZE) { + if (K == i7_mgl_OBJECT_TY) { + if ((((obj != 0) && ((story.i7_metaclass( obj) == story.i7_special_class_Object)))) && + (((i7_read_word(pr, 0) == 2) || (i7_provides(obj, pr))))) + return true; + } else { + if ((((obj >= 1)) && ((obj <= i7_read_word(i7_mgl_value_ranges, K))))) { + int holder = i7_read_word(i7_mgl_value_property_holders, K); + if (((holder !=0) && ((i7_provides(holder, pr))))) return true; + } + } + return false; + } + + internal int i7_read_gprop_value_inner(int K, int obj, int pr, + int i7_mgl_OBJECT_TY, int i7_mgl_value_ranges, + int i7_mgl_value_property_holders, int i7_mgl_COL_HSIZE) { + int val = 0; + if ((K == i7_mgl_OBJECT_TY)) { + return (int) i7_read_prop_value(obj, pr); + } else { + int holder = i7_read_word(i7_mgl_value_property_holders, K); + return (int) i7_read_word( + i7_read_prop_value(holder, pr), (obj + i7_mgl_COL_HSIZE)); + } + return val; + } + + internal void i7_write_gprop_value_inner(int K, int obj, int pr, + int val, int i7_mgl_OBJECT_TY, int i7_mgl_value_ranges, + int i7_mgl_value_property_holders, int i7_mgl_COL_HSIZE) { + if ((K == i7_mgl_OBJECT_TY)) { + i7_write_prop_value(obj, pr, val); + } else { + int holder = i7_read_word(i7_mgl_value_property_holders, K); + i7_write_word( + i7_read_prop_value(holder, pr), (obj + i7_mgl_COL_HSIZE), val); + } + } + + internal void i7_change_gprop_value_inner(int K, int obj, int pr, + int val, int form, + int i7_mgl_OBJECT_TY, int i7_mgl_value_ranges, + int i7_mgl_value_property_holders, int i7_mgl_COL_HSIZE) { + if ((K == i7_mgl_OBJECT_TY)) { + i7_change_prop_value(obj, pr, val, form); + } else { + int holder = i7_read_word(i7_mgl_value_property_holders, K); + i7_change_word( + i7_read_prop_value(holder, pr), (obj + i7_mgl_COL_HSIZE), val, form); + } + } +} +partial class Story { + public abstract int i7_gen_call(Inform.Process proc, int id, int[] args); +} + +partial class Process { + int i7_gen_call(int id, int[] args) { + return story.i7_gen_call(this, id, args); + } +} +partial class Process { + public int i7_call_0(int id) { + int[] args = new int[10]; + return i7_gen_call(id, args); + } + public int i7_call_1(int id, int v) { + int[] args = new int[10]; + args[0] = v; + return i7_gen_call(id, args); + } + public int i7_call_2(int id, int v, int v2) { + int[] args = new int[10]; + args[0] = v; args[1] = v2; + return i7_gen_call(id, args); + } + public int i7_call_3(int id, int v, int v2, + int v3) { + int[] args = new int[10]; + args[0] = v; args[1] = v2; args[2] = v3; + return i7_gen_call(id, args); + } + public int i7_call_4(int id, int v, int v2, + int v3, int v4) { + int[] args = new int[10]; + args[0] = v; args[1] = v2; args[2] = v3; args[3] = v4; + return i7_gen_call(id, args); + } + public int i7_call_5(int id, int v, int v2, + int v3, int v4, int v5) { + int[] args = new int[10]; + args[0] = v; args[1] = v2; args[2] = v3; args[3] = v4; args[4] = v5; + return i7_gen_call(id, args); + } +} +partial class Story { + protected internal int i7_var_self; +} + +partial class Process { + int i7_mcall_0(int to, int prop) { + int[] args = new int[0]; + int saved = state.variables[story.i7_var_self]; + state.variables[story.i7_var_self] = to; + int id = i7_read_prop_value(to, prop); + int rv = i7_gen_call(id, args); + state.variables[story.i7_var_self] = saved; + return rv; + } + + int i7_mcall_1(int to, int prop, int v) { + int[] args = new int[1]; + args[0] = v; + int saved = state.variables[story.i7_var_self]; + state.variables[story.i7_var_self] = to; + int id = i7_read_prop_value(to, prop); + int rv = i7_gen_call(id, args); + state.variables[story.i7_var_self] = saved; + return rv; + } + + int i7_mcall_2(int to, int prop, int v, + int v2) { + int[] args = new int[2]; + args[0] = v; args[1] = v2; + int saved = state.variables[story.i7_var_self]; + state.variables[story.i7_var_self] = to; + int id = i7_read_prop_value(to, prop); + int rv = i7_gen_call(id, args); + state.variables[story.i7_var_self] = saved; + return rv; + } + + int i7_mcall_3(int to, int prop, int v, + int v2, int v3) { + int[] args = new int[3]; + args[0] = v; args[1] = v2; args[2] = v3; + int saved = state.variables[story.i7_var_self]; + state.variables[story.i7_var_self] = to; + int id = i7_read_prop_value(to, prop); + int rv = i7_gen_call(id, args); + state.variables[story.i7_var_self] = saved; + return rv; + } +} +partial class Process { + public void i7_print_CLR_string(string clr_string) { + if (clr_string != null) + for (int i=0; i < clr_string.Length; i++) + i7_print_char((int) clr_string[i]); + } + + public void i7_print_decimal(int x) { + i7_print_CLR_string(x.ToString()); + } + + public void i7_print_object(int x) { + i7_print_decimal(x); + } + + public void i7_print_box(int x) { + Console.WriteLine("Unimplemented: i7_print_box."); + i7_fatal_exit(); + } + internal void i7_print_char(int x) { + if (x == 13) x = 10; + i7_push(x); + int current = 0; + i7_opcode_glk(GlkOpcodes.i7_glk_stream_get_current, 0, out current); + i7_push(current); + i7_opcode_glk(GlkOpcodes.i7_glk_put_char_stream, 2, out int _); + } + internal void i7_styling(int which, int what) { + stylist(this, which, what); + } + internal void i7_opcode_glk(int glk_api_selector, int varargc, + out int z) { + z = glk_implementation(this, glk_api_selector, varargc); + } +} +public static class GlkOpcodes { + public const int i7_glk_exit = 0x0001; + public const int i7_glk_set_interrupt_handler = 0x0002; + public const int i7_glk_tick = 0x0003; + public const int i7_glk_gestalt = 0x0004; + public const int i7_glk_gestalt_ext = 0x0005; + public const int i7_glk_window_iterate = 0x0020; + public const int i7_glk_window_get_rock = 0x0021; + public const int i7_glk_window_get_root = 0x0022; + public const int i7_glk_window_open = 0x0023; + public const int i7_glk_window_close = 0x0024; + public const int i7_glk_window_get_size = 0x0025; + public const int i7_glk_window_set_arrangement = 0x0026; + public const int i7_glk_window_get_arrangement = 0x0027; + public const int i7_glk_window_get_type = 0x0028; + public const int i7_glk_window_get_parent = 0x0029; + public const int i7_glk_window_clear = 0x002A; + public const int i7_glk_window_move_cursor = 0x002B; + public const int i7_glk_window_get_stream = 0x002C; + public const int i7_glk_window_set_echo_stream = 0x002D; + public const int i7_glk_window_get_echo_stream = 0x002E; + public const int i7_glk_set_window = 0x002F; + public const int i7_glk_window_get_sibling = 0x0030; + public const int i7_glk_stream_iterate = 0x0040; + public const int i7_glk_stream_get_rock = 0x0041; + public const int i7_glk_stream_open_file = 0x0042; + public const int i7_glk_stream_open_memory = 0x0043; + public const int i7_glk_stream_close = 0x0044; + public const int i7_glk_stream_set_position = 0x0045; + public const int i7_glk_stream_get_position = 0x0046; + public const int i7_glk_stream_set_current = 0x0047; + public const int i7_glk_stream_get_current = 0x0048; + public const int i7_glk_stream_open_resource = 0x0049; + public const int i7_glk_fileref_create_temp = 0x0060; + public const int i7_glk_fileref_create_by_name = 0x0061; + public const int i7_glk_fileref_create_by_prompt = 0x0062; + public const int i7_glk_fileref_destroy = 0x0063; + public const int i7_glk_fileref_iterate = 0x0064; + public const int i7_glk_fileref_get_rock = 0x0065; + public const int i7_glk_fileref_delete_file = 0x0066; + public const int i7_glk_fileref_does_file_exist = 0x0067; + public const int i7_glk_fileref_create_from_fileref = 0x0068; + public const int i7_glk_put_char = 0x0080; + public const int i7_glk_put_char_stream = 0x0081; + public const int i7_glk_put_string = 0x0082; + public const int i7_glk_put_string_stream = 0x0083; + public const int i7_glk_put_buffer = 0x0084; + public const int i7_glk_put_buffer_stream = 0x0085; + public const int i7_glk_set_style = 0x0086; + public const int i7_glk_set_style_stream = 0x0087; + public const int i7_glk_get_char_stream = 0x0090; + public const int i7_glk_get_line_stream = 0x0091; + public const int i7_glk_get_buffer_stream = 0x0092; + public const int i7_glk_char_to_lower = 0x00A0; + public const int i7_glk_char_to_upper = 0x00A1; + public const int i7_glk_stylehint_set = 0x00B0; + public const int i7_glk_stylehint_clear = 0x00B1; + public const int i7_glk_style_distinguish = 0x00B2; + public const int i7_glk_style_measure = 0x00B3; + public const int i7_glk_select = 0x00C0; + public const int i7_glk_select_poll = 0x00C1; + public const int i7_glk_request_line_event = 0x00D0; + public const int i7_glk_cancel_line_event = 0x00D1; + public const int i7_glk_request_char_event = 0x00D2; + public const int i7_glk_cancel_char_event = 0x00D3; + public const int i7_glk_request_mouse_event = 0x00D4; + public const int i7_glk_cancel_mouse_event = 0x00D5; + public const int i7_glk_request_timer_events = 0x00D6; + public const int i7_glk_image_get_info = 0x00E0; + public const int i7_glk_image_draw = 0x00E1; + public const int i7_glk_image_draw_scaled = 0x00E2; + public const int i7_glk_window_flow_break = 0x00E8; + public const int i7_glk_window_erase_rect = 0x00E9; + public const int i7_glk_window_fill_rect = 0x00EA; + public const int i7_glk_window_set_background_color = 0x00EB; + public const int i7_glk_schannel_iterate = 0x00F0; + public const int i7_glk_schannel_get_rock = 0x00F1; + public const int i7_glk_schannel_create = 0x00F2; + public const int i7_glk_schannel_destroy = 0x00F3; + public const int i7_glk_schannel_create_ext = 0x00F4; + public const int i7_glk_schannel_play_multi = 0x00F7; + public const int i7_glk_schannel_play = 0x00F8; + public const int i7_glk_schannel_play_ext = 0x00F9; + public const int i7_glk_schannel_stop = 0x00FA; + public const int i7_glk_schannel_set_volume = 0x00FB; + public const int i7_glk_sound_load_hint = 0x00FC; + public const int i7_glk_schannel_set_volume_ext = 0x00FD; + public const int i7_glk_schannel_pause = 0x00FE; + public const int i7_glk_schannel_unpause = 0x00FF; + public const int i7_glk_set_hyperlink = 0x0100; + public const int i7_glk_set_hyperlink_stream = 0x0101; + public const int i7_glk_request_hyperlink_event = 0x0102; + public const int i7_glk_cancel_hyperlink_event = 0x0103; + public const int i7_glk_buffer_to_lower_case_uni = 0x0120; + public const int i7_glk_buffer_to_upper_case_uni = 0x0121; + public const int i7_glk_buffer_to_title_case_uni = 0x0122; + public const int i7_glk_buffer_canon_decompose_uni = 0x0123; + public const int i7_glk_buffer_canon_normalize_uni = 0x0124; + public const int i7_glk_put_char_uni = 0x0128; + public const int i7_glk_put_string_uni = 0x0129; + public const int i7_glk_put_buffer_uni = 0x012A; + public const int i7_glk_put_char_stream_uni = 0x012B; + public const int i7_glk_put_string_stream_uni = 0x012C; + public const int i7_glk_put_buffer_stream_uni = 0x012D; + public const int i7_glk_get_char_stream_uni = 0x0130; + public const int i7_glk_get_buffer_stream_uni = 0x0131; + public const int i7_glk_get_line_stream_uni = 0x0132; + public const int i7_glk_stream_open_file_uni = 0x0138; + public const int i7_glk_stream_open_memory_uni = 0x0139; + public const int i7_glk_stream_open_resource_uni = 0x013A; + public const int i7_glk_request_char_event_uni = 0x0140; + public const int i7_glk_request_line_event_uni = 0x0141; + public const int i7_glk_set_echo_line_event = 0x0150; + public const int i7_glk_set_terminators_line_event = 0x0151; + public const int i7_glk_current_time = 0x0160; + public const int i7_glk_current_simple_time = 0x0161; + public const int i7_glk_time_to_date_utc = 0x0168; + public const int i7_glk_time_to_date_local = 0x0169; + public const int i7_glk_simple_time_to_date_utc = 0x016A; + public const int i7_glk_simple_time_to_date_local = 0x016B; + public const int i7_glk_date_to_time_utc = 0x016C; + public const int i7_glk_date_to_time_local = 0x016D; + public const int i7_glk_date_to_simple_time_utc = 0x016E; + public const int i7_glk_date_to_simple_time_local = 0x016F; +} +partial class Process { + public const int I7_BODY_TEXT_ID = 201; + public const int I7_STATUS_TEXT_ID = 202; + public const int I7_BOX_TEXT_ID = 203; + public const int i7_fileusage_Data = 0x00; + public const int i7_fileusage_SavedGame = 0x01; + public const int i7_fileusage_Transcript = 0x02; + public const int i7_fileusage_InputRecord = 0x03; + public const int i7_fileusage_TypeMask = 0x0f; + public const int i7_fileusage_TextMode = 0x100; + public const int i7_fileusage_BinaryMode = 0x000; + + public const int i7_filemode_Write = 0x01; + public const int i7_filemode_Read = 0x02; + public const int i7_filemode_ReadWrite = 0x03; + public const int i7_filemode_WriteAppend = 0x05; + public const int i7_seekmode_Start = (0); + public const int i7_seekmode_Current = (1); + public const int i7_seekmode_End = (2); + public const int i7_evtype_None = 0; + public const int i7_evtype_Timer = 1; + public const int i7_evtype_CharInput = 2; + public const int i7_evtype_LineInput = 3; + public const int i7_evtype_MouseInput = 4; + public const int i7_evtype_Arrange = 5; + public const int i7_evtype_Redraw = 6; + public const int i7_evtype_SoundNotify = 7; + public const int i7_evtype_Hyperlink = 8; + public const int i7_evtype_VolumeNotify = 9; +} +public static class GlkGestalts { + public const int i7_gestalt_Version = 0; + public const int i7_gestalt_CharInput = 1; + public const int i7_gestalt_LineInput = 2; + public const int i7_gestalt_CharOutput = 3; + public const int i7_gestalt_CharOutput_ApproxPrint = 1; + public const int i7_gestalt_CharOutput_CannotPrint = 0; + public const int i7_gestalt_CharOutput_ExactPrint = 2; + public const int i7_gestalt_MouseInput = 4; + public const int i7_gestalt_Timer = 5; + public const int i7_gestalt_Graphics = 6; + public const int i7_gestalt_DrawImage = 7; + public const int i7_gestalt_Sound = 8; + public const int i7_gestalt_SoundVolume = 9; + public const int i7_gestalt_SoundNotify = 10; + public const int i7_gestalt_Hyperlinks = 11; + public const int i7_gestalt_HyperlinkInput = 12; + public const int i7_gestalt_SoundMusic = 13; + public const int i7_gestalt_GraphicsTransparency = 14; + public const int i7_gestalt_Unicode = 15; + public const int i7_gestalt_UnicodeNorm = 16; + public const int i7_gestalt_LineInputEcho = 17; + public const int i7_gestalt_LineTerminators = 18; + public const int i7_gestalt_LineTerminatorKey = 19; + public const int i7_gestalt_DateTime = 20; + public const int i7_gestalt_Sound2 = 21; + public const int i7_gestalt_ResourceStream = 22; + public const int i7_gestalt_GraphicsCharInput = 23; +} +partial class Defaults { + public static int i7_default_glk(Process proc, int selector, int varargc) { + proc.i7_debug_stack("i7_opcode_glk"); + int[] a = { 0, 0, 0, 0, 0 }; + int argc = 0; + while (varargc > 0) { + int v = proc.i7_pull(); + if (argc < 5) a[argc++] = v; + varargc--; + } + + int rv = 0; + switch (selector) { + case GlkOpcodes.i7_glk_gestalt: + rv = proc.i7_miniglk_gestalt(a[0]); break; + + /* Characters */ + case GlkOpcodes.i7_glk_char_to_lower: + rv = proc.i7_miniglk_char_to_lower(a[0]); break; + case GlkOpcodes.i7_glk_char_to_upper: + rv = proc.i7_miniglk_char_to_upper(a[0]); break; + case i7_glk_buffer_to_lower_case_uni: + for (int pos=0; pos= 0x41) && (c <= 0x5A)) || + ((c >= 0xC0) && (c <= 0xD6)) || + ((c >= 0xD8) && (c <= 0xDE))) c += 32; + return c; + } + + internal int i7_miniglk_char_to_upper(int c) { + if (((c >= 0x61) && (c <= 0x7A)) || + ((c >= 0xE0) && (c <= 0xF6)) || + ((c >= 0xF8) && (c <= 0xFE))) c -= 32; + return c; + } +} +class MiniGlkData { + internal const int I7_MINIGLK_MAX_FILES = 128; + internal const int I7_MINIGLK_MAX_STREAMS = 128; + internal const int I7_MINIGLK_MAX_WINDOWS = 128; + internal const int I7_MINIGLK_RING_BUFFER_SIZE = 32; + /* streams */ + internal MgStream[] memory_streams; + int stdout_stream_id, stderr_stream_id; + /* files */ + internal MgFile[] files; + internal int no_files; + /* windows */ + internal MgWindow[] windows; + internal int no_windows; + /* events */ + internal MgEvent[] events_ring_buffer; + internal int rb_back, rb_front; + internal int no_line_events; + + internal MiniGlkData(Process proc) { + memory_streams = new MgStream[MiniGlkData.I7_MINIGLK_MAX_STREAMS]; + for (int i=0; i= MiniGlkData.I7_MINIGLK_MAX_FILES) { + Console.Error.WriteLine("Out of files"); i7_fatal_exit(); + } + int id = miniglk.no_files++; + miniglk.files[id].usage = 0; + miniglk.files[id].name = 0; + miniglk.files[id].rock = 0; + miniglk.files[id].handle = null; + miniglk.files[id].leafname = null; + return id; + } + + long i7_mg_fseek(int id, int pos, int origin) { + if ((id < 0) || (id >= MiniGlkData.I7_MINIGLK_MAX_FILES)) { + Console.Error.WriteLine("Bad file ID"); i7_fatal_exit(); + } + if (miniglk.files[id].handle == null) { + Console.Error.WriteLine("File not open"); i7_fatal_exit(); + } + return miniglk.files[id].handle.Seek(pos, (SeekOrigin)origin); + } + + long i7_mg_ftell(int id) { + if ((id < 0) || (id >= MiniGlkData.I7_MINIGLK_MAX_FILES)) { + Console.Error.WriteLine("Bad file ID"); i7_fatal_exit(); + } + if (miniglk.files[id].handle == null) { + Console.Error.WriteLine("File not open"); i7_fatal_exit(); + } + long t = miniglk.files[id].handle.Position; + return t; + } + + int i7_mg_fopen(int id, int mode) { + if ((id < 0) || (id >= MiniGlkData.I7_MINIGLK_MAX_FILES)) { + Console.Error.WriteLine("Bad file ID"); i7_fatal_exit(); + } + if (miniglk.files[id].handle != null) { + Console.Error.WriteLine("File already open"); i7_fatal_exit(); + } + FileAccess access = FileAccess.Read; + FileMode n_mode = FileMode.Open; + switch (mode) { + case Process.i7_filemode_Write: access = FileAccess.Write; n_mode = FileMode.Create; break; + case Process.i7_filemode_Read: access = FileAccess.Read; n_mode = FileMode.Open; break; + case Process.i7_filemode_ReadWrite: access = FileAccess.ReadWrite; n_mode = FileMode.OpenOrCreate; break; + case Process.i7_filemode_WriteAppend: access = FileAccess.Write; n_mode = FileMode.OpenOrCreate; break; + } + FileStream h = File.Open(miniglk.files[id].leafname, n_mode, access); + if (h == null) return 0; + miniglk.files[id].handle = h; + if (mode == Process.i7_filemode_WriteAppend) i7_mg_fseek( id, 0, (int)SeekOrigin.End); + return 1; + } + + void i7_mg_fclose(int id) { + if ((id < 0) || (id >= MiniGlkData.I7_MINIGLK_MAX_FILES)) { + Console.Error.WriteLine("Bad file ID"); i7_fatal_exit(); + } + if (miniglk.files[id].handle == null) { + Console.Error.WriteLine("File not open"); i7_fatal_exit(); + } + miniglk.files[id].handle.Close(); + miniglk.files[id].handle = null; + } + + + void i7_mg_fputc(int c, int id) { + if ((id < 0) || (id >= MiniGlkData.I7_MINIGLK_MAX_FILES)) { + Console.Error.WriteLine("Bad file ID"); i7_fatal_exit(); + } + if (miniglk.files[id].handle == null) { + Console.Error.WriteLine("File not open"); i7_fatal_exit(); + } + miniglk.files[id].handle.WriteByte((byte)c); + } + + int i7_mg_fgetc(int id) { + if ((id < 0) || (id >= MiniGlkData.I7_MINIGLK_MAX_FILES)) { + Console.Error.WriteLine("Bad file ID"); i7_fatal_exit(); + } + if (miniglk.files[id].handle == null) { + Console.Error.WriteLine("File not open"); i7_fatal_exit(); + } + int c = miniglk.files[id].handle.ReadByte(); + return c; + } + internal int i7_miniglk_fileref_create_by_name(int usage, + int name, int rock) { + int id = i7_mg_new_file(); + miniglk.files[id].usage = usage; + miniglk.files[id].name = name; + miniglk.files[id].rock = rock; + + var L = new System.Text.StringBuilder(); + + for (int i=0; i < 127; i++) { + //FIXME: not unicode safe + char b = (char) i7_read_byte(name+1+i); + if (b == 0) break; + L.Append(b); + } + + L.Append(".glkdata"); + miniglk.files[id].leafname = L.ToString(); + return id; + } + + internal int i7_miniglk_fileref_does_file_exist(int id) { + if ((id < 0) || (id >= MiniGlkData.I7_MINIGLK_MAX_FILES)) { + Console.Error.WriteLine("Bad file ID"); i7_fatal_exit(); + } + if (miniglk.files[id].handle != null) return 1; + if (i7_mg_fopen( id, Process.i7_filemode_Read) != 0) { + i7_mg_fclose( id); return 1; + } + return 0; + } + internal static MgStream i7_mg_new_stream(Stream F, int win_id) { + MgStream S = new MgStream(); + S.to_file = F; + S.to_file_id = -1; + S.to_memory = null; + S.memory_used = 0; + S.memory_capacity = 0; + S.write_here_on_closure = 0; + S.write_limit = 0; + S.previous_id = 0; + S.active = 0; + S.encode_UTF8 = 0; + S.char_size = 4; + S.chars_read = 0; + S.read_position = 0; + S.end_position = 0; + S.owned_by_window_id = win_id; + S.style = null; + S.fixed_pitch = 0; + S.composite_style = null; + return S; + } + + internal int i7_mg_open_stream(Stream F, int win_id) { + for (int i=0; i= MiniGlkData.I7_MINIGLK_MAX_STREAMS)) { + Console.Error.WriteLine("Stream ID {0} out of range", id); i7_fatal_exit(); + } + + if (miniglk.memory_streams[id].to_file_id >= 0) { + int origin = 0; + switch (seekmode) { + case i7_seekmode_Start: origin = (int)SeekOrigin.Begin; break; + case i7_seekmode_Current: origin = (int)SeekOrigin.Current; break; + case i7_seekmode_End: origin = (int)SeekOrigin.End; break; + default: Console.Error.WriteLine("Unknown seekmode"); i7_fatal_exit(); break; + } + i7_mg_fseek( miniglk.memory_streams[id].to_file_id, pos, origin); + } else { + Console.Error.WriteLine("glk_stream_set_position supported only for file streams"); + i7_fatal_exit(); + } + } + + internal int i7_miniglk_stream_get_position(int id) { + if ((id < 0) || (id >= MiniGlkData.I7_MINIGLK_MAX_STREAMS)) { + Console.Error.WriteLine("Stream ID {0} out of range", id); i7_fatal_exit(); + } + + if (miniglk.memory_streams[id].to_file_id >= 0) { + return (int) i7_mg_ftell( miniglk.memory_streams[id].to_file_id); + } + return (int) miniglk.memory_streams[id].memory_used; + } + internal int i7_miniglk_stream_get_current() { + return state.current_output_stream_ID; + } + + internal void i7_miniglk_stream_set_current(int id) { + if ((id < 0) || (id >= MiniGlkData.I7_MINIGLK_MAX_STREAMS)) { + Console.Error.WriteLine("Stream ID {0} out of range", id); i7_fatal_exit(); + } + state.current_output_stream_ID = id; + } + internal void i7_mg_put_to_stream(int rock, char c) { + + if (receiver == null) Console.OpenStandardOutput().WriteByte((byte) c); + else receiver(rock, c, miniglk.memory_streams[state.current_output_stream_ID].composite_style); + } + + internal void i7_miniglk_put_char_stream(int stream_id, int x) { + if (miniglk.memory_streams[stream_id].to_file != null) { + int win_id = miniglk.memory_streams[stream_id].owned_by_window_id; + int rock = -1; + if (win_id >= 1) rock = i7_mg_get_window_rock( win_id); + uint c = (uint) x; + if (use_UTF8 != 0) { + if (c >= 0x200000) { /* invalid Unicode */ + i7_mg_put_to_stream(rock, '?'); + } else if (c >= 0x10000) { + i7_mg_put_to_stream(rock, 0xF0 + (c >> 18)); + i7_mg_put_to_stream(rock, 0x80 + ((c >> 12) & 0x3f)); + i7_mg_put_to_stream(rock, 0x80 + ((c >> 6) & 0x3f)); + i7_mg_put_to_stream(rock, 0x80 + (c & 0x3f)); + } + if (c >= 0x800) { + i7_mg_put_to_stream( rock, (char) (0xE0 + (c >> 12))); + i7_mg_put_to_stream( rock, (char) (0x80 + ((c >> 6) & 0x3f))); + i7_mg_put_to_stream( rock, (char) (0x80 + (c & 0x3f))); + } else if (c >= 0x80) { + i7_mg_put_to_stream( rock, (char) (0xC0 + (c >> 6))); + i7_mg_put_to_stream( rock, (char) (0x80 + (c & 0x3f))); + } else i7_mg_put_to_stream( rock, (char) c); + } else { + i7_mg_put_to_stream( rock, (char) c); + } + } else if (miniglk.memory_streams[stream_id].to_file_id >= 0) { + i7_mg_fputc( (int) x, miniglk.memory_streams[stream_id].to_file_id); + miniglk.memory_streams[stream_id].end_position++; + } else { + if (miniglk.memory_streams[stream_id].memory_used >= miniglk.memory_streams[stream_id].memory_capacity) { + long needed = 4*miniglk.memory_streams[stream_id].memory_capacity; + if (needed == 0) needed = 1024; + byte[] new_data = new byte[needed]; + if (new_data == null) { + Console.Error.WriteLine("Out of memory"); i7_fatal_exit(); + } + for (long i=0; i= 0) { + miniglk.memory_streams[stream_id].chars_read++; + return i7_mg_fgetc( miniglk.memory_streams[stream_id].to_file_id); + } + return 0; + } + + internal void i7_miniglk_stream_close(int id, int result) { + if ((id < 0) || (id >= MiniGlkData.I7_MINIGLK_MAX_STREAMS)) { + Console.Error.WriteLine("Stream ID {0} out of range", id); i7_fatal_exit(); + } + if (id == 0) { Console.Error.WriteLine("Cannot close stdout"); i7_fatal_exit(); } + if (id == 1) { Console.Error.WriteLine("Cannot close stderr"); i7_fatal_exit(); } + if (miniglk.memory_streams[id].active == 0) { + Console.Error.WriteLine("Stream {0} already closed", id); i7_fatal_exit(); + } + if (state.current_output_stream_ID == id) + state.current_output_stream_ID = miniglk.memory_streams[id].previous_id; + if (miniglk.memory_streams[id].write_here_on_closure != 0) { + if (miniglk.memory_streams[id].char_size == 4) { + for (int i = 0; i < miniglk.memory_streams[id].write_limit; i++) + if (i < miniglk.memory_streams[id].memory_used) + i7_write_word(miniglk.memory_streams[id].write_here_on_closure, i, miniglk.memory_streams[id].to_memory[i]); + else + i7_write_word(miniglk.memory_streams[id].write_here_on_closure, i, 0); + } else { + for (int i = 0; i < miniglk.memory_streams[id].write_limit; i++) + if (i < miniglk.memory_streams[id].memory_used) + i7_write_byte(miniglk.memory_streams[id].write_here_on_closure + i, miniglk.memory_streams[id].to_memory[i]); + else + i7_write_byte(miniglk.memory_streams[id].write_here_on_closure + i, 0); + } + } + if (result == -1) { + i7_push(miniglk.memory_streams[id].chars_read); + i7_push(miniglk.memory_streams[id].memory_used); + } else if (result != 0) { + i7_write_word(result, 0, miniglk.memory_streams[id].chars_read); + i7_write_word(result, 1, miniglk.memory_streams[id].memory_used); + } + if (miniglk.memory_streams[id].to_file_id >= 0) i7_mg_fclose( miniglk.memory_streams[id].to_file_id); + miniglk.memory_streams[id].active = 0; + miniglk.memory_streams[id].memory_used = 0; + } + + internal int i7_miniglk_window_open(int split, int method, + int size, int wintype, int rock) { + if (miniglk.no_windows >= 128) { + Console.Error.WriteLine("Out of windows"); i7_fatal_exit(); + } + int id = miniglk.no_windows++; + miniglk.windows[id].type = wintype; + miniglk.windows[id].stream_id = i7_mg_open_stream( Console.OpenStandardOutput(), id); + miniglk.windows[id].rock = rock; + return id; + } + + internal int i7_miniglk_set_window(int id) { + if ((id < 0) || (id >= miniglk.no_windows)) { + Console.Error.WriteLine("Window ID {0} out of range", id); i7_fatal_exit(); + } + i7_miniglk_stream_set_current( miniglk.windows[id].stream_id); + return 0; + } + + internal int i7_mg_get_window_rock(int id) { + if ((id < 0) || (id >= miniglk.no_windows)) { + Console.Error.WriteLine("Window ID {0} out of range", id); i7_fatal_exit(); + } + return miniglk.windows[id].rock; + } + + internal int i7_miniglk_window_get_size(int id, int a1, + int a2) { + if (a1 != 0) i7_write_word(a1, 0, 80); + if (a2 != 0) i7_write_word(a2, 0, 8); + return 0; + } + void i7_mg_add_event_to_buffer(MgEvent e) { + miniglk.events_ring_buffer[miniglk.rb_front] = e; + miniglk.rb_front++; + if (miniglk.rb_front == MiniGlkData.I7_MINIGLK_RING_BUFFER_SIZE) + miniglk.rb_front = 0; + } + + MgEvent i7_mg_get_event_from_buffer() { + if (miniglk.rb_front == miniglk.rb_back) return null; + MgEvent e = miniglk.events_ring_buffer[miniglk.rb_back]; + miniglk.rb_back++; + if (miniglk.rb_back == MiniGlkData.I7_MINIGLK_RING_BUFFER_SIZE) + miniglk.rb_back = 0; + return e; + } + internal int i7_miniglk_select(int/* TODO bool*/ structure) { + MgEvent e = i7_mg_get_event_from_buffer(); + if (e == null) { + Console.Error.WriteLine("No events available to select"); i7_fatal_exit(); + } + if (structure == -1) { + i7_push(e.type); + i7_push(e.win_id); + i7_push(e.val1); + i7_push(e.val2); + } else { + if (structure != 0) { + i7_write_word(structure, 0, e.type); + i7_write_word(structure, 1, e.win_id); + i7_write_word(structure, 2, e.val1); + i7_write_word(structure, 3, e.val2); + } + } + return 0; + } + + internal int i7_miniglk_request_line_event(int window_id, + int buffer, int max_len, int init_len) { + MgEvent e = new MgEvent(); + e.type = Process.i7_evtype_LineInput; + e.win_id = window_id; + e.val1 = 1; + e.val2 = 0; + char c; int pos = init_len; + if (sender == null) i7_benign_exit(); + string s = sender(send_count++); + int i = 0; + while (true) { + c = s[i++]; + if ((c == -1) || (c == 0) || (c == '\n') || (c == '\r')) break; + if (pos < max_len) i7_write_byte(buffer + pos++, (byte) c); + } + if (pos < max_len) i7_write_byte(buffer + pos, 0); + else i7_write_byte(buffer + max_len-1, 0); + e.val1 = pos; + i7_mg_add_event_to_buffer(e); + if (miniglk.no_line_events++ == 1000) { + Console.WriteLine("[Too many line events: terminating to prevent hang]"); + i7_benign_exit(); + } + return 0; + } + + internal int i7_miniglk_request_line_event_uni(int window_id, + int buffer, int max_len, int init_len) { + MgEvent e = new MgEvent(); + e.type = Process.i7_evtype_LineInput; + e.win_id = window_id; + e.val1 = 1; + e.val2 = 0; + char c; int pos = init_len; + if (sender == null) i7_benign_exit(); + string s = sender(send_count++); + int i = 0; + while (1) { + c = s[i++]; + if ((c == EOF) || (c == 0) || (c == '\n') || (c == '\r')) break; + if (pos < max_len) i7_write_word(buffer, pos++, c); + } + if (pos < max_len) i7_write_word(buffer, pos, 0); + else i7_write_word(proc, buffer, max_len-1, 0); + e.val1 = pos; + i7_mg_add_event_to_buffer(e); + if (pminiglk.no_line_events++ == 1000) { + Console.WriteLine("[Too many line events: terminating to prevent hang]\n"); + i7_benign_exit(); + } + return 0; + } +} + +partial class Defaults { + public static void i7_default_stylist(Process proc, int which, int what) { + if (which == 1) { + proc.miniglk.memory_streams[proc.state.current_output_stream_ID].fixed_pitch = what; + } else { + proc.miniglk.memory_streams[proc.state.current_output_stream_ID].style = null; + switch (what) { + case 0: break; + case 1: proc.miniglk.memory_streams[proc.state.current_output_stream_ID].style = "bold"; break; + case 2: proc.miniglk.memory_streams[proc.state.current_output_stream_ID].style = "italic"; break; + case 3: proc.miniglk.memory_streams[proc.state.current_output_stream_ID].style = "reverse"; break; + default: { + #if i7_mgl_BASICINFORMKIT + int L = + i7_fn_TEXT_TY_CharacterLength( what, 0, 0, 0, 0, 0, 0); + if (L > 127) L = 127; + for (int i=0; i((_, i7_val) => i7_val)("); VNODE_1C; WRITE(","); VNODE_2C; WRITE(")"); break; + case TERNARYSEQUENTIAL_BIP: + /* FIXME: This is probably very inefficient. */ + WRITE("/*tseq*/new System.Func((_, _, i7_val) => i7_val)("); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(", "); + VNODE_3C; WRITE(")"); break; + case RANDOM_BIP: + WRITE("proc.i7_random("); VNODE_1C; WRITE(")"); break; + default: return NOT_APPLICABLE; + } + return FALSE; +} + +@h add, sub, neg, mul, div, mod. +Also the functions |i7_div| and |i7_mod|, which are just wrappers for their +opcodes but return values rather than copying to pointers. + +The implementations of |@div| and |@mod| are borrowed from the glulxe +reference code, to be sure that we have the right sign conventions. + += (text to inform7_cslib.cs) +partial class Process { + internal void i7_opcode_add(int x, int y, out int z) { + z = x + y; + } + internal void i7_opcode_sub(int x, int y, out int z) { + z = x - y; + } + internal void i7_opcode_neg(int x, out int y) { + y = -x; + } + internal void i7_opcode_mul(int x, int y, out int z) { + z = x * y; + } + + internal void i7_opcode_div(int x, int y, out int z) { + if (y == 0) { Console.WriteLine("Division of {0:D} by 0", x); i7_fatal_exit(); z=0; return; } + int result, ax, ay; + if (x < 0) { + ax = (-x); + if (y < 0) { + ay = (-y); + result = ax / ay; + } else { + ay = y; + result = -(ax / ay); + } + } else { + ax = x; + if (y < 0) { + ay = (-y); + result = -(ax / ay); + } else { + ay = y; + result = ax / ay; + } + } + z = result; + } + + internal void i7_opcode_mod(int x, int y, out int z) { + if (y == 0) { Console.WriteLine("Division of {0:D} by 0", x); z=0; i7_fatal_exit(); return; } + int result, ax, ay = (y < 0)?(-y):y; + if (x < 0) { + ax = (-x); + result = -(ax % ay); + } else { + ax = x; + result = ax % ay; + } + z = result; + } + + internal int i7_div(int x, int y) { + int z; + i7_opcode_div(x, y, out z); + return z; + } + + internal int i7_mod(int x, int y) { + int z; + i7_opcode_mod(x, y, out z); + return z; + } += + +@h fadd, fsub, fmul, fdiv, fmod. +The remaining opcodes are for floating-point arithmetic, and it all seems +straightforward, since we just want to use standard C# |float| arithmetic +throughout, but the devil is in the details. The code below is heavily +indebted to Andrew Plotkin. + += (text to inform7_cslib.cs) + internal void i7_opcode_fadd(int x, int y, out int z) { + z = i7_encode_float(i7_decode_float(x) + i7_decode_float(y)); + } + internal void i7_opcode_fsub(int x, int y, out int z) { + z = i7_encode_float(i7_decode_float(x) - i7_decode_float(y)); + } + internal void i7_opcode_fmul(int x, int y, out int z) { + z = i7_encode_float(i7_decode_float(x) * i7_decode_float(y)); + } + internal void i7_opcode_fdiv(int x, int y, out int z) { + z = i7_encode_float(i7_decode_float(x) / i7_decode_float(y)); + } + internal void i7_opcode_fmod(int x, int y, out int z, out int w) { + float fx = i7_decode_float(x), fy = i7_decode_float(y); + float fquot = fx % fy; + int quot = i7_encode_float(fquot); + int rem = i7_encode_float((fx-fquot) / fy); + if (rem == 0x0 || rem == unchecked((int)0x80000000)) { + /* When the quotient is zero, the sign has been lost in the + shuffle. We'll set that by hand, based on the original arguments. */ + rem = (x ^ y) & unchecked((int)0x80000000); + } + z = quot; + w = rem; + } + +@h floor, ceil, ftonumn, ftonumz, numtof. +All of which are conversions between integer and floating-point values. + += (text to inform7_cslib.cs) + internal void i7_opcode_floor(int x, out int y) { + y = i7_encode_float((float)Math.Floor(i7_decode_float(x))); + } + internal void i7_opcode_ceil(int x, out int y) { + y = i7_encode_float((float)Math.Ceiling(i7_decode_float(x))); + } + + internal void i7_opcode_ftonumn(int x, out int y) { + float fx = i7_decode_float(x); + int result; + if (Math.Sign(fx) > 1) { + if (float.IsNaN(fx) || float.IsInfinity(fx) || (fx > 2147483647.0)) + result = 0x7FFFFFFF; + else + result = (int) (Math.Round(fx)); + } + else { + if (float.IsNaN(fx) || float.IsInfinity(fx) || (fx < -2147483647.0)) + result = unchecked((int)0x80000000); + else + result = (int) (Math.Round(fx)); + } + y = result; + } + + internal void i7_opcode_ftonumz(int x, out int y) { + float fx = i7_decode_float(x); + int result; + if (Math.Sign(fx) > 1) { + if (float.IsNaN(fx) || float.IsInfinity(fx) || (fx > 2147483647.0)) + result = 0x7FFFFFFF; + else + result = (int) (Math.Truncate(fx)); + } + else { + if (float.IsNaN(fx) || float.IsInfinity(fx) || (fx < -2147483647.0)) + result = unchecked((int)0x80000000); + else + result = (int) (Math.Truncate(fx)); + } + y = result; + } + + internal void i7_opcode_numtof(int x, out int y) { + y = i7_encode_float((float) x); + } += + +@h exp, log, pow, sqrt. + += (text to inform7_cslib.cs) + internal void i7_opcode_exp(int x, out int y) { + y = i7_encode_float((float)Math.Exp(i7_decode_float(x))); + } + internal void i7_opcode_log(int x, out int y) { + y = i7_encode_float((float)Math.Log(i7_decode_float(x))); + } + internal void i7_opcode_pow(int x, int y, out int z) { + if (i7_decode_float(x) == 1.0f) + z = i7_encode_float(1.0f); + else if ((i7_decode_float(y) == 0.0f) || (i7_decode_float(y) == -0.0f)) + z = i7_encode_float(1.0f); + else if ((i7_decode_float(x) == -1.0f) && float.IsInfinity(i7_decode_float(y))) + z = i7_encode_float(1.0f); + else + z = i7_encode_float((float)Math.Pow(i7_decode_float(x), i7_decode_float(y))); + } + internal void i7_opcode_sqrt(int x, out int y) { + y = i7_encode_float((float)Math.Sqrt(i7_decode_float(x))); + } += + +@h sin, cos, tan, asin, acos, atan. + += (text to inform7_cslib.cs) + internal void i7_opcode_sin(int x, out int y) { + y = i7_encode_float((float)Math.Sin(i7_decode_float(x))); + } + internal void i7_opcode_cos(int x, out int y) { + y = i7_encode_float((float)Math.Cos(i7_decode_float(x))); + } + internal void i7_opcode_tan(int x, out int y) { + y = i7_encode_float((float)Math.Tan(i7_decode_float(x))); + } + + internal void i7_opcode_asin(int x, out int y) { + y = i7_encode_float((float)Math.Asin(i7_decode_float(x))); + } + internal void i7_opcode_acos(int x, out int y) { + y = i7_encode_float((float)Math.Acos(i7_decode_float(x))); + } + internal void i7_opcode_atan(int x, out int y) { + y = i7_encode_float((float)Math.Atan(i7_decode_float(x))); + } + +@h jfeq. jfne, jfge, jflt, jisinf, jisnan. +These are branch instructions of the kind which spook anybody who's never +looked at how floating-point arithmetic is actually done. Once you stop +thinking of a |float| as a number and start thinking of it as a sort of fuzzy +uncertainty range all of this becomes more explicable, but still. + += (text to inform7_cslib.cs) + internal int i7_opcode_jfeq(int x, int y, int z) { + int result; + if ((z & 0x7F800000) == 0x7F800000 && (z & 0x007FFFFF) != 0) { + /* The delta is NaN, which can never match. */ + result = 0; + } else if ((x == 0x7F800000 || (uint) x == 0xFF800000) + && (y == 0x7F800000 || (uint) y == 0xFF800000)) { + /* Both are infinite. Opposite infinities are never equal, + even if the difference is infinite, so this is easy. */ + result = (x == y) ? 1 : 0; + } else { + float fx = i7_decode_float(y) - i7_decode_float(x); + float fy = System.Math.Abs(i7_decode_float(z)); + result = (fx <= fy && fx >= -fy) ? 1 : 0; + } + return result; + } + + internal int i7_opcode_jfne(int x, int y, int z) { + int result; + if ((z & 0x7F800000) == 0x7F800000 && (z & 0x007FFFFF) != 0) { + /* The delta is NaN, which can never match. */ + result = 0; + } else if ((x == 0x7F800000 || (uint) x == 0xFF800000) + && (y == 0x7F800000 || (uint) y == 0xFF800000)) { + /* Both are infinite. Opposite infinities are never equal, + even if the difference is infinite, so this is easy. */ + result = (x == y) ? 1 : 0; + } else { + float fx = i7_decode_float(y) - i7_decode_float(x); + float fy = System.Math.Abs(i7_decode_float(z)); + result = (fx <= fy && fx >= -fy) ? 1 : 0; + } + return result; + } + + internal int i7_opcode_jfge(int x, int y) { + if (i7_decode_float(x) >= i7_decode_float(y)) return 1; + return 0; + } + + internal int i7_opcode_jflt(int x, int y) { + if (i7_decode_float(x) < i7_decode_float(y)) return 1; + return 0; + } + + internal int i7_opcode_jisinf(int x) { + if (x == 0x7F800000 || (uint) x == 0xFF800000) return 1; + return 0; + } + + internal int i7_opcode_jisnan(int x) { + if ((x & 0x7F800000) == 0x7F800000 && (x & 0x007FFFFF) != 0) return 1; + return 0; + } +} += diff --git a/inter/final-module/Chapter 6/C# Assembly.w b/inter/final-module/Chapter 6/C# Assembly.w new file mode 100644 index 0000000000..ebef978bb1 --- /dev/null +++ b/inter/final-module/Chapter 6/C# Assembly.w @@ -0,0 +1,757 @@ +[CSAssembly::] C# Assembly. + +The problem of assembly language. + +@h General implementation. +This section does just one thing: compiles invocations of assembly-language +opcodes. + += +void CSAssembly::initialise(code_generator *gtr) { + METHOD_ADD(gtr, INVOKE_OPCODE_MTID, CSAssembly::invoke_opcode); + METHOD_ADD(gtr, ASSEMBLY_MARKER_MTID, CSAssembly::assembly_marker); +} + +typedef struct CS_generation_assembly_data { + struct dictionary *opcodes_used; +} CS_generation_assembly_data; + +void CSAssembly::initialise_data(code_generation *gen) { + CS_GEN_DATA(asmdata.opcodes_used) = NULL; +} + +void CSAssembly::begin(code_generation *gen) { + CSAssembly::initialise_data(gen); +} + +void CSAssembly::end(code_generation *gen) { +} + +@ Inter is for the most part fully specified and cross-platform, but assembly +language is the big hole in that. It is legal for Inter code to contain almost +anything which purports to be assembly language. For example, the following +code will successfully build as part of an Inter kit: += (text as Inform 6) + [ Peculiar x; + @bandersnatch x; + ]; += +Kit code, and also material included in |(-| and |-)| brackets in I7 source text, +can claim to use assembly language opcodes with any names it likes. No checking +is done that these are "real" opcodes. (Spoilers: |@bandersnatch| is not.) + +The point of this is that different final targets support different sets of +assembly language. This was always true for Inform 6 code (after around 2000, +anyway), because the Z and Glulx virtual machines had different assembly +languages: |@split_window| exists for Z but not Glulx, |@atan| exists for +Glulx but not Z, for example. + +So each different final generator needs to make its own decision about what +assembly language opcodes to provide, and what they will do. In theory, we could +make an entirely new assembly language for C. But in practice that would just +make the standard Inform kits, such as BasicInformKit, impossible to support +on C, because those kits make quite heavy use of opcodes from Z/Glulx. + +We will instead: + +(1) Emulate exactly that subset of the Glulx assembly language which is used +by the standard Inform kits, and +(2) Allow any other opcodes to be externally defined by the user. + +In this way, we obtain both compatibility with the Inform kits, enabling us to +compile works of IF to C, and also extensibility. + +@ Each different opcode we see will be matched up to a //CS_supported_opcode// +giving it some metadata: we will gather these into a dictionary so that names +of opcodes can quickly be resolved to their metadata structures. + +That dictionary will begin with (1) about 60 standard supported opcodes, but +then may pick up (2) a few others such as |@bandersnatch|, if kits do something +non-standard. + +So now we define some very minimal metadata on our opcodes. Each opcode will, +when used, be followed by a number of operands, which we number from 1: += (text as Inform 6) + @fmod a b rem quot; + ! 1 2 3 4 += +This opcode, which performs floating-point division with remainder, reads in +operands 1 and 2, and writes results out to operands 3 and 4. In the following, +|store_this_operand[3]| and |store_this_operand[4]| would be |TRUE|, while +|store_this_operand[1]| and |store_this_operand[2]| would be |FALSE|. (In fact, +this is an outlier, because it is the only opcode we support which has more than +one store operand. But in principle we could have many.) + +Glulx assembly language also allows variable numbers of arguments to some opcodes, +or "varargs". For example: += (text as Inform 6) + @glk 4 _vararg_count ret; + ! 1 2 3 += +Here operand 3 is a store, and operands 1 and 2 are read in. But operand 2 is +special in that it is a count of additional operands which are found on the +stack rather than in the body of the instruction. For example, += (text as Inform 6) + @glk 4 6 ret; += +would provide |@glk| with seven operands to read in: the one in the instruction +itself, |4|, and then the top 6 items on the stack. + +Because of this, an operand holding a variable-argument count is special. There +can be at most one for any opcode; |vararg_operand| is -1 if there isn't one, +but for |@glk|, |vararg_operand| would be 2. + += +typedef struct CS_supported_opcode { + struct text_stream *name; /* including the opening |@| character */ + int store_this_operand[MAX_OPERANDS_IN_INTER_ASSEMBLY]; + int vararg_operand; /* position of |_vararg_count| operand, or -1 if none */ + int speculative; /* i.e., not part of the standard supported set */ + CLASS_DEFINITION +} CS_supported_opcode; + +@ On creation, a //CS_supported_opcode// is automatically added to the dictionary: + += +CS_supported_opcode *CSAssembly::new_opcode(code_generation *gen, text_stream *name, + int s1, int s2, int va) { + CS_supported_opcode *opc = CREATE(CS_supported_opcode); + opc->speculative = FALSE; + opc->name = Str::duplicate(name); + for (int i=0; istore_this_operand[i] = FALSE; + if (s1 >= 1) opc->store_this_operand[s1] = TRUE; + if (s2 >= 1) opc->store_this_operand[s2] = TRUE; + opc->vararg_operand = va; + Dictionaries::create(CS_GEN_DATA(asmdata.opcodes_used), name); + Dictionaries::write_value(CS_GEN_DATA(asmdata.opcodes_used), name, opc); + return opc; +} + +@ When the generator encounters an opcode called |name| which seems to be used +with |operand_count| operands, it calls the following function to find the +corresponding metadata. Note that this always returns a valid //CS_supported_opcode//, +because even if a completely unexpected name is encountered, the above +mechanism will just create a meaning for it. + += +CS_supported_opcode *CSAssembly::find_opcode(code_generation *gen, text_stream *name, + int operand_count) { + if (CS_GEN_DATA(asmdata.opcodes_used) == NULL) { + CS_GEN_DATA(asmdata.opcodes_used) = Dictionaries::new(256, FALSE); + @; + } + CS_supported_opcode *opc; + if (Dictionaries::find(CS_GEN_DATA(asmdata.opcodes_used), name)) { + opc = Dictionaries::read_value(CS_GEN_DATA(asmdata.opcodes_used), name); + } else { + @; + } + return opc; +} + +@ = + CSAssembly::new_opcode(gen, I"@acos", 2, -1, -1); + CSAssembly::new_opcode(gen, I"@add", 3, -1, -1); + CSAssembly::new_opcode(gen, I"@aload", 3, -1, -1); + CSAssembly::new_opcode(gen, I"@aloadb", 3, -1, -1); + CSAssembly::new_opcode(gen, I"@aloads", 3, -1, -1); + CSAssembly::new_opcode(gen, I"@asin", 2, -1, -1); + CSAssembly::new_opcode(gen, I"@atan", 2, -1, -1); + CSAssembly::new_opcode(gen, I"@binarysearch", 8, -1, -1); + CSAssembly::new_opcode(gen, I"@call", 3, -1, 2); + CSAssembly::new_opcode(gen, I"@ceil", 2, -1, -1); + CSAssembly::new_opcode(gen, I"@copy", 2, -1, -1); + CSAssembly::new_opcode(gen, I"@cos", 2, -1, -1); + CSAssembly::new_opcode(gen, I"@div", 3, -1, -1); + CSAssembly::new_opcode(gen, I"@exp", 2, -1, -1); + CSAssembly::new_opcode(gen, I"@fadd", 3, -1, -1); + CSAssembly::new_opcode(gen, I"@fdiv", 3, -1, -1); + CSAssembly::new_opcode(gen, I"@floor", 2, -1, -1); + CSAssembly::new_opcode(gen, I"@fmod", 3, 4, -1); + CSAssembly::new_opcode(gen, I"@fmul", 3, -1, -1); + CSAssembly::new_opcode(gen, I"@fsub", 3, -1, -1); + CSAssembly::new_opcode(gen, I"@ftonumn", 2, -1, -1); + CSAssembly::new_opcode(gen, I"@ftonumz", 2, -1, -1); + CSAssembly::new_opcode(gen, I"@gestalt", 3, -1, -1); + CSAssembly::new_opcode(gen, I"@glk", 3, -1, 2); + CSAssembly::new_opcode(gen, I"@hasundo", 1, -1, -1); + CSAssembly::new_opcode(gen, I"@jeq", -1, -1, -1); + CSAssembly::new_opcode(gen, I"@jfeq", -1, -1, -1); + CSAssembly::new_opcode(gen, I"@jfge", -1, -1, -1); + CSAssembly::new_opcode(gen, I"@jflt", -1, -1, -1); + CSAssembly::new_opcode(gen, I"@jisinf", -1, -1, -1); + CSAssembly::new_opcode(gen, I"@jisnan", -1, -1, -1); + CSAssembly::new_opcode(gen, I"@jleu", -1, -1, -1); + CSAssembly::new_opcode(gen, I"@jnz", -1, -1, -1); + CSAssembly::new_opcode(gen, I"@jz", -1, -1, -1); + CSAssembly::new_opcode(gen, I"@log", 2, -1, -1); + CSAssembly::new_opcode(gen, I"@malloc", -1, -1, -1); + CSAssembly::new_opcode(gen, I"@mcopy", -1, -1, -1); + CSAssembly::new_opcode(gen, I"@mzero", -1, -1, -1); + CSAssembly::new_opcode(gen, I"@mfree", -1, -1, -1); + CSAssembly::new_opcode(gen, I"@mod", 3, -1, -1); + CSAssembly::new_opcode(gen, I"@mul", 3, -1, -1); + CSAssembly::new_opcode(gen, I"@neg", 2, -1, -1); + CSAssembly::new_opcode(gen, I"@nop", -1, -1, -1); + CSAssembly::new_opcode(gen, I"@numtof", 2, -1, -1); + CSAssembly::new_opcode(gen, I"@pow", 3, -1, -1); + CSAssembly::new_opcode(gen, I"@quit", -1, -1, -1); + CSAssembly::new_opcode(gen, I"@random", 2, -1, -1); + CSAssembly::new_opcode(gen, I"@restart", -1, -1, -1); + CSAssembly::new_opcode(gen, I"@restore", 2, -1, -1); + CSAssembly::new_opcode(gen, I"@restoreundo", 1, -1, -1); + CSAssembly::new_opcode(gen, I"@return", -1, -1, -1); + CSAssembly::new_opcode(gen, I"@save", 2, -1, -1); + CSAssembly::new_opcode(gen, I"@saveundo", 1, -1, -1); + CSAssembly::new_opcode(gen, I"@setiosys", -1, -1, -1); + CSAssembly::new_opcode(gen, I"@setrandom", -1, -1, -1); + CSAssembly::new_opcode(gen, I"@shiftl", 3, -1, -1); + CSAssembly::new_opcode(gen, I"@sin", 2, -1, -1); + CSAssembly::new_opcode(gen, I"@sqrt", 2, -1, -1); + CSAssembly::new_opcode(gen, I"@streamchar", -1, -1, -1); + CSAssembly::new_opcode(gen, I"@streamnum", -1, -1, -1); + CSAssembly::new_opcode(gen, I"@streamunichar", -1, -1, -1); + CSAssembly::new_opcode(gen, I"@sub", 3, -1, -1); + CSAssembly::new_opcode(gen, I"@tan", 2, -1, -1); + CSAssembly::new_opcode(gen, I"@ushiftr", 3, -1, -1); + CSAssembly::new_opcode(gen, I"@verify", 1, -1, -1); + +@ Speculative opcodes cannot store and cannot have varargs. Also, since they +are not part of our supported set, there's no code here to implement them. +Instead we predeclare a function and simply assume that the user will have +written this function somewhere and linked it to us. For example, we might +predeclare this: += (text as C) + void i7_opcode_bandersnatch(int v1); += + +@ = + opc = CSAssembly::new_opcode(gen, name, -1, -1, -1); + opc->speculative = TRUE; + segmentation_pos saved = CodeGen::select(gen, cs_predeclarations_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("void "); + CSNamespace::mangle_opcode(OUT, name); + WRITE("(Inform.Process proc"); + for (int i=1; i<=operand_count; i++) WRITE(", int v%d", i); + WRITE(");\n"); + CodeGen::deselect(gen, saved); + +@ We finally have enough infrastructure to invoke a general assembly-language +instruction found in our Inter. + += +void CSAssembly::invoke_opcode(code_generator *gtr, code_generation *gen, + text_stream *opcode, int operand_count, inter_tree_node **operands, + inter_tree_node *label, int label_sense) { + CS_supported_opcode *opc = CSAssembly::find_opcode(gen, opcode, operand_count); + text_stream *OUT = CodeGen::current(gen); + if (label_sense != NOT_APPLICABLE) @; + int push_store[MAX_OPERANDS_IN_INTER_ASSEMBLY]; + for (int i=0; i; + if (label_sense != NOT_APPLICABLE) @; + @; + WRITE(";\n"); +} + +@ = + WRITE("if /*as*/(System.Convert.ToBoolean("); + +@ = + WRITE(")"); + if (label_sense == FALSE) WRITE(" == false"); + WRITE(") goto "); + if (label == NULL) internal_error("no branch label"); + Vanilla::node(gen, label); + +@ Each instruction becomes a function call to the function implementing the +opcode in question, except that |@return| becomes the C statement |return|. +If the opcode has N operands then the function has N+1 arguments, since the +first is always the process pointer. + +It may seem to compile slow code if we turn instructions into function calls, but +(a) assembly is not used very much in Inter code, and then not for time-sensitive +operations, and +(b) the C compiler receiving the code we generate will almost certainly perform +inline optimisation to remove most of these calls anyway. + +@ = + if (Str::eq(opcode, I"@return")) { + WRITE("return ("); + } else { + WRITE("proc."); CSNamespace::mangle_opcode(OUT, opcode); WRITE("("); + } + for (int operand = 1; operand <= operand_count; operand++) { + if (operand > 1) WRITE(", "); + TEMPORARY_TEXT(O) + CodeGen::select_temporary(gen, O); + Vanilla::node(gen, operands[operand-1]); + CodeGen::deselect_temporary(gen); + if (opc->store_this_operand[operand]) @ + else @; + DISCARD_TEXT(O) + } + WRITE(")"); + +@ The argument for a regular operand will have type |int|, so we have to +compile something of that type here. + +The special operand notation |sp| is a pseudo-variable meaning "the top of the +stack", so if we see that then we compile that to a pull: note that |i7_pull| +returns an |int|. + +@ = + if (Str::eq(O, I"sp")) { + WRITE("proc.i7_pull()"); + } else { + WRITE("%S", O); + } + +@ The argument for a store operand will have type |out int|, so now we have +to make a pointer. + +Again, |sp| is a pseudo-variable meaning "the top of the stack", but this time +we have to push, not pull, and that's something we can't do until the function +has returned -- the function will create the value we need to push. We get +around this by compiling a pointer to some temporary memory. + +Finally, assembly also allows |0| as a special value for a store operand, and +this means "throw the value away". We don't want to incur a C compiler warning +by attempting to write |0| in a pointer context, so we pass it as |NULL| instead. + +@ = + if (Str::eq(O, I"sp")) { + WRITE("out (proc.state.tmp[%d])", operand); + push_store[operand] = TRUE; + } else if (Str::eq(O, I"0")) { + WRITE("out int _"); + } else { + WRITE("out %S", O); + } + +@ That may leave a few stored results stranded in temporary workspace, so: + +@ = + for (int operand = 1; operand <= operand_count; operand++) + if (push_store[operand]) + WRITE("; proc.i7_push(proc.state.tmp[%d])", operand); + +@ And where does the special operand |sp| come from? From here: + += +void CSAssembly::assembly_marker(code_generator *gtr, code_generation *gen, inter_ti marker) { + text_stream *OUT = CodeGen::current(gen); + switch (marker) { + case ASM_SP_ASMMARKER: WRITE("sp"); break; + default: + WRITE_TO(STDERR, "Unsupported assembly marker is '%d'\n", marker); + internal_error("unsupported assembly marker in C"); + } +} + +@h call. +That does everything except to implement the standard set of opcodes, which +must be done with about 60 functions in the C library. + +This is not the place to specify what Glulx opcodes do. See Andrew Plotkin's +//documentation on the Glulx virtual machine -> https://www.eblong.com/zarf/glulx//. + +Most of the opcodes we support are defined below, but see also //C Input-Output Model// +for |@glk|, and see //C Arithmetic// for the plethora of mathematical operations +such as |@fmul|. + +To begin, here is a |@call|, which performs a function call to a perhaps computed +address: + += (text to inform7_cslib.cs) +partial class Process { + internal void i7_opcode_call(int fn_ref, int varargc, out int z) { + int[] args = new int[varargc]; + for (int i=0; i= 0) && (y < 32)) value = (x << y); + z = value; + } + + internal void i7_opcode_ushiftr(int x, int y, out int z) { + int value = 0; + if ((y >= 0) && (y < 32)) value = (x >> y); + z = value; + } += + +@h jeq, jleu, jnz, jz. +These are branch opcodes and return an |int|. + +The implementation of |@jleu| uses an explicitly unchecked cast. This code is +unlikely to ever be compiled with checks on by default, but it doesn't hurt to +use |unchecked| just in case. + += (text to inform7_cslib.cs) + internal int i7_opcode_jeq(int x, int y) { + if (x == y) return 1; + return 0; + } + + internal int i7_opcode_jleu(int x, int y) { + uint ux, uy; + ux = unchecked((uint) x); uy = unchecked((uint) y); + if (ux <= uy) return 1; + return 0; + } + + internal int i7_opcode_jnz(int x) { + if (x != 0) return 1; + return 0; + } + + internal int i7_opcode_jz(int x) { + if (x == 0) return 1; + return 0; + } += + +@h nop, quit, verify. +There is no real meaning for |@verify| in this situation: it's supposed to +check the checksum for the contents of a virtual machine, to protect against +the (entirely likely) scenario of a floppy disk sector going bad in 1983. +So we unconditionally store the "okay" result. + += (text to inform7_cslib.cs) + internal void i7_opcode_nop() { + } + + internal void i7_opcode_quit() { + i7_fatal_exit(); + } + + internal void i7_opcode_verify(out int z) { + z = 0; + } += + +@h restoreundo, saveundo, hasundo, discardundo. +This all works, but we do something pretty inelegant to support |@restoreundo|: +we insert a call to a (presumably kit-based) function called |DealWithUndo|, +provided this exists. This is done because we are unable safely to follow the +proper Glulx specification. In principle, after a |@restoreundo| succeeds, +execution immediately continues from the position in the program where the +|@saveundo| occurred. For a while the implementation here imitated this by +using |longjmp| and |setjmp|, but it all proved very fragile because of the +difficulty of storing |setjmp| positions safely in memory. + +Correspondingly, our implementation of |@saveundo| always stores the result +value 0. The result value 1 would indicate that execution had switched there +from a successful |@restoreundo|: but, as noted, that never happens. + += (text to inform7_cslib.cs) + internal void i7_opcode_restoreundo(out int x) { + if (i7_has_snapshot()) { + i7_restore_snapshot(); + x = 0; + #if i7_mgl_DealWithUndo + i7_fn_DealWithUndo(this); + #endif + } else { + x = 1; + } + } + + internal void i7_opcode_saveundo(out int x) { + i7_save_snapshot(); + x = 0; + } + + internal void i7_opcode_hasundo(out int x) { + int rv = 0; if (i7_has_snapshot()) rv = 1; + x = rv; + } + + internal void i7_opcode_discardundo() { + i7_destroy_latest_snapshot(); + } += + +@h restart, restore, save. +For the moment, at least, we intentionally do not implement these. It seems +likely that anyone using C# to run interactive fiction is doing so in a wider +framework where saved states will work differently from the traditional model +of asking the user for a filename and then saving data out to a binary file +of that name in the current working directory. Better to do nothing here, and +let users handle this themselves. + +Similar considerations apply to |@restart|. The intention of this opcode is +essentially to reboot the virtual machine and start over: here, though, we +have a real machine. It's easy enough to reinitialise the process state, +but not so simple to restart execution as if from a clean process start. + + += (text to inform7_cslib.cs) + internal void i7_opcode_restart() { + Console.WriteLine("(RESTART is not implemented on this C# program.)"); + } + + internal void i7_opcode_restore(int x, out int y) { + Console.WriteLine("(RESTORE is not implemented on this C# program.)"); + y = 1; + } + + internal void i7_opcode_save(int x, out int y) { + Console.WriteLine("(SAVE is not implemented on this C# program.)"); + y = 1; + } += + +@h streamchar, streamnum, streamunichar. + + += (text to inform7_cslib.cs) + internal void i7_opcode_streamnum(int x) { + i7_print_decimal(x); + } + + internal void i7_opcode_streamchar(int x) { + i7_print_char(x); + } + + internal void i7_opcode_streamunichar(int x) { + i7_print_char(x); + } += + +@h binarysearch. +This is a Grand Imperial Hotel among Glulx opcodes, with 8 operands, only the +last of which is a store. It performs a binary search on a block of structures +known to be sorted already. It has a nice general-purpose look but was devised so +that command verbs could be looked up quickly in dictionary tables when interactive +fiction is being played: that's the only use which the standard Inform kits make +of it. + +The elegant implementation here comes from Andrew Plotkin's reference code for +|glulxe|, a Glulx interpreter. |options| is a bitmap of the bits defined below. +In the only use the standard Inform kits make of this opcode, |options| will be +just |serop_KeyIndirect|, but |keysize| will be more than 4, so that the elaborate +speed optimisation for keys of size 1, 2 and 4, and thus |keybuf|, are never used. +But we may as well have the full functionality here. + + += (text to inform7_cslib.cs) + const int serop_KeyIndirect = 1; + const int serop_ZeroKeyTerminates = 2; + const int serop_ReturnIndex = 4; + + internal void i7_opcode_binarysearch(int key, int keysize, + int start, int structsize, int numstructs, int keyoffset, + int options, out int s1) { + + /* If the key size is 4 or fewer, copy it directly into the keybuf array */ + byte[] keybuf = new byte[4]; + if ((options & serop_KeyIndirect) != 0) { + if (keysize <= 4) + for (int ix=0; ix byte2) cmp = 1; + } + } else { + for (int ix=0; (cmp == 0) && ix byte2) cmp = 1; + } + } + + if (cmp == 0) { + /* Success! */ + if ((options & serop_ReturnIndex) != 0) s1 = val; else s1 = addr; + return; + } + + if (cmp < 0) bot = val+1; /* Chop search range to the second half */ + else top = val; /* Chop search range to the first half */ + } + + /* Failure! */ + if ((options & serop_ReturnIndex) != 0) s1 = -1; else s1 = 0; + } += + +@h mcopy, mzero, malloc, mfree. +A Glulx assembly opcode is provided for fast memory copies, which we must +implement. We're choosing not to implement the Glulx |@malloc| or |@mfree| +opcodes for now, but that will surely need to change in due course. (When that +does change, we will need also to change |@gestalt|.) + += (text to inform7_cslib.cs) + internal void i7_opcode_mcopy(int x, int y, int z) { + if (z < y) + for (int i=0; i=0; i--) + i7_write_byte(z+i, i7_read_byte(y+i)); + } + + internal void i7_opcode_mzero(int x, int y) { + for (int i=0; i> 16) & 0x7fff; + } + uint value; + if (x == 0) value = rawvalue; + else if (x >= 1) value = rawvalue % (uint) (x); + else value = (uint) -(rawvalue % (uint) (-x)); + y = (int) value; + } + + internal void i7_opcode_setrandom(int s) { + if (s == 0) { + state.seed.A = (uint) DateTime.Now.Ticks; + state.seed.interval = 0; + } else if (s < 1000) { + state.seed.interval = (uint) s; + state.seed.counter = 0; + } else { + state.seed.A = (uint) s; + state.seed.interval = 0; + } + } + + internal int i7_random(int x) { + int r; + i7_opcode_random(x, out r); + return r+1; + } += + +@h setiosys. +This opcode in principle allows a story file to select the input-output system +it will use. But the Inform kits only use system 2, called Glk, and this is the +only system we support, so we will simply ignore this. + += (text to inform7_cslib.cs) + internal void i7_opcode_setiosys(int x, int y) { + } += + +@h gestalt. +This opcode allows a story file to ask the Glulx interpreter running it whether +or not the interpreter can perform certain tasks. + += (text to inform7_cslib.cs) + internal void i7_opcode_gestalt(int x, int y, out int z) { + int r = 0; + switch (x) { + case 0: r = 0x00030103; break; /* Say that the Glulx version is v3.1.3 */ + case 1: r = 1; break; /* Say that the interpreter version is 1 */ + case 2: r = 0; break; /* We do not (yet) support @setmemsize */ + case 3: r = 1; break; /* We do support UNDO */ + case 4: if (y == 2) r = 1; /* We do support Glk */ + else r = 0; /* But not any other I/O system */ + break; + case 5: r = 1; break; /* We do support Unicode operations */ + case 6: r = 1; break; /* We do support @mzero and @mcopy */ + case 7: r = 0; break; /* We do not (yet) support @malloc or @mfree */ + case 8: r = 0; break; /* Since we do not support @malloc */ + case 9: r = 0; break; /* We do not support @accelfunc pr @accelparam */ + case 10: r = 0; break; /* And therefore provide none of their accelerants */ + case 11: r = 1; break; /* We do support floating-point maths operations */ + case 12: r = 1; break; /* We do support @hasundo and @discardundo */ + } + z = r; + } +} += diff --git a/inter/final-module/Chapter 6/C# Conditions.w b/inter/final-module/Chapter 6/C# Conditions.w new file mode 100644 index 0000000000..dc4ddeff81 --- /dev/null +++ b/inter/final-module/Chapter 6/C# Conditions.w @@ -0,0 +1,143 @@ +[CSConditions::] CS Conditions. + +Evaluating conditions. + +@ This section implements the primitives which evaluate conditions. |!propertyvalue| +might seem a surprising inclusion in the list: as the name suggests, this finds +a property value. But although it is often used in a value context, it's also used +as a condition. For example, if kit code (written in Inform 6 notation) does this: += (text as Inform 6) + if (obj has concealed) ... += +then the condition amounts to an |inv !propertyvalue|. Now, since any value can +be used as a condition, this may still not seem to mean that |!propertyvalue| +belongs here; but consider that it is also legal to write -- += (text as Inform 6) + if (obj has concealed or scenery) ... += +Here the |inv !propertyvalue| involves an |inv !alternative| in its children, +and handling that requires the mechanism below. + += +int CSConditions::invoke_primitive(code_generation *gen, inter_ti bip, inter_tree_node *P) { + text_stream *OUT = CodeGen::current(gen); + switch (bip) { + case NOT_BIP: + WRITE("System.Convert.ToInt32(!System.Convert.ToBoolean("); VNODE_1C; WRITE("))"); break; + case AND_BIP: + WRITE("System.Convert.ToInt32(System.Convert.ToBoolean("); VNODE_1C; WRITE(") && System.Convert.ToBoolean("); VNODE_2C; WRITE("))"); break; + case OR_BIP: + WRITE("System.Convert.ToInt32(System.Convert.ToBoolean("); VNODE_1C; WRITE(") || System.Convert.ToBoolean("); VNODE_2C; WRITE("))"); break; + case PROPERTYEXISTS_BIP: + CS_GEN_DATA(objdata.value_ranges_needed) = TRUE; + CS_GEN_DATA(objdata.value_property_holders_needed) = TRUE; + WRITE("(i7_provides_gprop(proc, "); VNODE_1C; WRITE(", "); + VNODE_2C; WRITE(", "); VNODE_3C; WRITE("))"); + break; + case EQ_BIP: case NE_BIP: case GT_BIP: case GE_BIP: case LT_BIP: case LE_BIP: + case OFCLASS_BIP: case IN_BIP: case NOTIN_BIP: + WRITE("/*lotsabips*/"); + CSConditions::comparison_r(gen, bip, NULL, + InterTree::first_child(P), InterTree::second_child(P), 0); + break; + case PROPERTYVALUE_BIP: + CSConditions::comparison_r(gen, bip, InterTree::first_child(P), + InterTree::second_child(P), InterTree::third_child(P), 0); + break; + case ALTERNATIVE_BIP: + internal_error("misplaced !alternative in Inter tree"); break; + default: return NOT_APPLICABLE; + } + return FALSE; +} + +@ The following recursive mechanism exists because of the need to support +alternative choices in Inter conditions, as here: += (text as Inter) + inv !if + inv !eq + val K_number x + inv !alternative + val K_number 4 + val K_number 8 + ... += +This is the equivalent of writing |if (x == 4 or 8) ...| in Inform 6, but C does +not have an |or| operator like that. We could with care sometimes compile this +as |if ((x == 4) || (x == 8))|, but if evaluating |x| has side-effects, or is +slow, this will cause problems. Instead we compile |if (t = x, ((t == 4) || (t == 8)))| +where |t| is temporary storage. + +Note that |!ne| and |!notin| interpret |!alternative| in a de Morgan-like way, +so that we compile |if ((x != 4) && (x != 8))| rather than |if ((x != 4) || (x != 8))|. +The former is equivalent to negating |!eq| on the same choices, which is what we want; +the latter would be universally true, which is useless. + += +void CSConditions::comparison_r(code_generation *gen, + inter_ti bip, inter_tree_node *K, inter_tree_node *X, inter_tree_node *Y, int depth) { + if (Inode::is(Y, INV_IST)) { + if (InvInstruction::method(Y) == PRIMITIVE_INVMETH) { + inter_symbol *prim = InvInstruction::primitive(Y); + inter_ti ybip = Primitives::to_BIP(gen->from, prim); + if (ybip == ALTERNATIVE_BIP) { + text_stream *OUT = CodeGen::current(gen); + if (depth == 0) { + WRITE("(proc.state.tmp[0] = "); Vanilla::node(gen, X); WRITE(", ("); + } + WRITE("System.Convert.ToBoolean("); + CSConditions::comparison_r(gen, bip, K, NULL, InterTree::first_child(Y), depth+1); + if ((bip == NE_BIP) || (bip == NOTIN_BIP)) WRITE(") && System.Convert.ToBoolean("); + else WRITE(") /*cr*/|| System.Convert.ToBoolean("); + CSConditions::comparison_r(gen, bip, K, NULL, InterTree::second_child(Y), depth+1); + WRITE(")"); + if (depth == 0) { WRITE("))"); } + return; + } + } + } + text_stream *OUT = CodeGen::current(gen); + int positive = TRUE; + text_stream *test_fn = NULL, *test_operator = NULL; + WRITE("System.Convert.ToInt32"); + switch (bip) { + case OFCLASS_BIP: positive = TRUE; test_fn = I"i7_ofclass"; break; + case IN_BIP: positive = TRUE; test_fn = I"i7_in"; break; + case NOTIN_BIP: positive = FALSE; test_fn = I"i7_in"; break; + case EQ_BIP: test_operator = I"=="; break; + case NE_BIP: test_operator = I"!="; break; + case GT_BIP: test_operator = I">"; break; + case GE_BIP: test_operator = I">="; break; + case LT_BIP: test_operator = I"<"; break; + case LE_BIP: test_operator = I"<="; break; + case PROPERTYVALUE_BIP: break; + default: internal_error("unsupported condition"); break; + } + + if (bip == PROPERTYVALUE_BIP) { + WRITE("(i7_read_gprop_value(proc, ", test_fn); + Vanilla::node(gen, K); WRITE(", "); + @; + WRITE(", "); + @; + WRITE("))"); + } else if (Str::len(test_fn) > 0) { + WRITE("(proc.%S(", test_fn); + @; + WRITE(", "); + @; + WRITE(")"); + if (positive == FALSE) WRITE(" == 0"); + WRITE(")"); + } else { + WRITE("("); @; + WRITE(" %S ", test_operator); + @; WRITE(")"); + } +} + +@ = + if (X) Vanilla::node(gen, X); else WRITE("proc.state.tmp[0]"); + +@ = + Vanilla::node(gen, Y); diff --git a/inter/final-module/Chapter 6/C# Function Model.w b/inter/final-module/Chapter 6/C# Function Model.w new file mode 100644 index 0000000000..0f85373f4b --- /dev/null +++ b/inter/final-module/Chapter 6/C# Function Model.w @@ -0,0 +1,685 @@ +[CSFunctionModel::] C# Function Model. + +Translating functions into C#, and the calling conventions needed for them. + +@h Introduction. + += +void CSFunctionModel::initialise(code_generator *gtr) { + METHOD_ADD(gtr, PREDECLARE_FUNCTION_MTID, CSFunctionModel::predeclare_function); + METHOD_ADD(gtr, DECLARE_FUNCTION_MTID, CSFunctionModel::declare_function); + METHOD_ADD(gtr, PLACE_LABEL_MTID, CSFunctionModel::place_label); + METHOD_ADD(gtr, EVALUATE_LABEL_MTID, CSFunctionModel::evaluate_label); + METHOD_ADD(gtr, INVOKE_FUNCTION_MTID, CSFunctionModel::invoke_function); +} + +typedef struct CS_generation_function_model_data { + int compiling_function; + struct dictionary *predeclared_external_functions; +} CS_generation_function_model_data; + +void CSFunctionModel::initialise_data(code_generation *gen) { + CS_GEN_DATA(fndata.compiling_function) = FALSE; + CS_GEN_DATA(fndata.predeclared_external_functions) = Dictionaries::new(1024, TRUE); +} + +void CSFunctionModel::begin(code_generation *gen) { + CSFunctionModel::initialise_data(gen); +} + +void CSFunctionModel::end(code_generation *gen) { + CSFunctionModel::write_gen_call(gen); + CSFunctionModel::write_inward_calling_wrappers(gen); +} + +@h Predeclaration. +So, then: each Inter function will lead to a corresponding C# method. The C +analogue of this method goes to some effort to declare the functions before +they are used, but this is unnecessary in C#: + += +void CSFunctionModel::predeclare_function(code_generator *gtr, code_generation *gen, + vanilla_function *vf) { + CSFunctionModel::declare_function_constant(gen, vf); +} + +@ And this expresses what the C# prototype for that function will be. For example, +suppose we have an Inter function which arises from a kit function reading like so: += (text as Inform 6) + [ HelloThere greeting x y; + ... + ]; += +Now in practice this function may always be called as |HelloThere("Aloha!")| or +similar, with the local variable |greeting| being an argument, and the other two +locals |x| and |y| being used only internally. But Inter does not distinguish between +arguments and private locals; and indeed it permits the same function to be called +with differing numbers of arguments. |HelloThere("Bonjour!", 31)| is a legal call, +and results in |x| initially being 31 rather than 0. + +Because of this, our C analogue must also be callable with a variable number of +arguments. Now, of course, C does have a crude mechanism for this (used for |printf| +and similar), but it's nowhere near flexible enough to handle what we need. +Instead we declare our C function like so: += (text as Inform 6) + int i7_fn_HelloThere(Inform.Process proc, int i7_mgl_local_greeting, + int i7_mgl_local_x, int i7_mgl_local_y) { + ... + } += +And we then make calls like |i7_fn_HelloThere(proc, X, 0, 0)| or +|i7_fn_HelloThere(proc, X, Y, 0)| to simulate calling this with one or two +arguments respectively. Because unsupplied arguments are filled in as 0, we achieve +the Inter convention that any local variables not used as call arguments are set +to 0 at the start of a function. While this generates C code which does not look +especially pretty, it works efficiently in practice. + +We give the return type as |int|. In Inter, there is no such thing as a void +function: all functions return something, even if that something is meaningless +and is then thrown away. + += +void CSFunctionModel::CS_function_identifier(code_generation *gen, OUTPUT_STREAM, + vanilla_function *vf) { + CSNamespace::mangle_with(NULL, OUT, vf->identifier, I"fn"); +} + +void CSFunctionModel::CS_function_prototype(code_generation *gen, OUTPUT_STREAM, + vanilla_function *vf) { + if (Str::eq(vf->identifier, I"Main")) WRITE("override public "); + WRITE("int "); + CSFunctionModel::CS_function_identifier(gen, OUT, vf); + WRITE("(Inform.Process proc"); + text_stream *local_name; + LOOP_OVER_LINKED_LIST(local_name, text_stream, vf->locals) { + WRITE(", int "); + Generators::mangle(gen, OUT, local_name); + } + WRITE(")"); +} + +@h Functions as values. +A function is a value in Inter, and can be stored in variables, or arrays, or +properties, and so on. The Vanilla algorithm expects that the mangled name of +the function identifier will evaluate to this value. + +But what is this value to be? The obvious thing would be to represent the Inter +function at runtime by a pointer to the C function it has become. We could then +dereference that pointer to perform a function call, and so on. But this doesn't +work, because C does not allow function pointers to be used in a constant context. + +This is why the function //CSFunctionModel::CS_function_identifier// writes an +identifier which is _not_ the same as the mangled function name. Instead, the +mangled function name is defined as a constant, as follows. + += +void CSFunctionModel::declare_function_constant(code_generation *gen, vanilla_function *vf) { + segmentation_pos saved = CodeGen::select(gen, cs_function_predeclarations_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("const int "); + CSNamespace::mangle(NULL, OUT, vf->identifier); + /* WRITE(";\n"); + CodeGen::deselect(gen, saved); + saved = CodeGen::select(gen, cs_constructor_I7CGS); + OUT = CodeGen::current(gen); + CSNamespace::mangle(NULL, OUT, vf->identifier); */ + WRITE(" = (I7VAL_FUNCTIONS_BASE + %d);\n", vf->allocation_id); + CodeGen::deselect(gen, saved); +} + +@h Function calls. +First, the straightforward and most common case: calling a function whose identity +is known at run-time, and is passed to us as |vf|. + += +void CSFunctionModel::invoke_function(code_generator *gtr, code_generation *gen, + inter_tree_node *P, vanilla_function *vf, int void_context) { + text_stream *OUT = CodeGen::current(gen); + CSFunctionModel::CS_function_identifier(gen, OUT, vf); + WRITE("/*if*/(proc"); + if (vf->takes_variable_arguments) @ + else @; + WRITE(")"); + if (void_context) WRITE(";\n"); +} + +@ As noted above, all unsupplied call parameters are filled in with zeroes: + +@ = + int c = 0; + LOOP_THROUGH_INTER_CHILDREN(F, P) { + WRITE(", "); Vanilla::node(gen, F); + c++; + } + for (; c < vf->max_arity; c++) WRITE(", 0"); + +@ A handful of functions, always supplied by kits (i.e., never compiled directly +by Inform 7), use a stack-based calling mechanism instead. For example: += (text as Inform 6) +[ whatever _vararg_count ret; + ... +]; += +The significant thing here is that the first local variable is called |_vararg_count|. +The Inform 6 compiler reacts to that by using a different function call mechanism; +the function call |whatever(x, y, z)| will then result in |x|, |y|, |z| being pushed +onto the stack, while execution in the function begins with |_vararg_count| equal +to 3 (the number of things pushed), and |ret| equal to 0. + +Note that the arguments must be pushed in reverse order -- |z|, |y|, |x| -- in +order to ensure that the first one, |x|, is at the top of the stack when execution +of the function begins. + +But of course a C compiler will not automatically do that just because the first +local happens to be called |_vararg_count|, so we must simulate the effect here. + +In practice, the maximum number of variable arguments needed is seldom more than +about 3 and never more than 10 in I7 usage, so the maximum here is not at all +restrictive. + +@d MAX_VARARG_COUNT 128 + +@ = + inter_tree_node *args[MAX_VARARG_COUNT]; + int c = 0; + LOOP_THROUGH_INTER_CHILDREN(F, P) if (c < MAX_VARARG_COUNT) args[c++] = F; + WRITE(", ((System.Func)(()=>{"); + for (int i=c-1; i >= 0; i--) { + WRITE("proc.i7_push("); + Vanilla::node(gen, args[i]); + WRITE("); "); + } + WRITE("return %d;}))()", c); + for (int i=1; i < vf->max_arity; i++) WRITE(", 0"); + +@ Now the harder case: calling a function whose identity is not known at compile +time, but which is identified only as a runtime value which will be one of the +numbers defined above. This is done with a function called |i7_gen_call|, and +that is what we must now compile: + += (text to inform7_cslib.cs) +partial class Story { + public abstract int i7_gen_call(Inform.Process proc, int id, int[] args); +} + +partial class Process { + int i7_gen_call(int id, int[] args) { + return story.i7_gen_call(this, id, args); + } +} += + +The structure is basically one big switch. In simplified pseudocode it looks like so: += (text as C) + switch (function_id_number) { + case id1: rv = fn1(proc, arg1); break; + case id2: rv = fn2(proc, arg1, arg2, arg3); break; + ... + } += +That seems inefficient: instead, why not store the addresses of the relevant +functions in a lookup table? After all, the case ID numbers are just a consecutive +run of integers. + +But we can't do that because in the C standard there is no safe way to cast or +store those pointer types in a way which would safely be a union of the possible +function types. All of the things which probably work on most architectures are +formally "undefined behavior" in C99; we cannot, for example, assume that function +pointers have the same size (in the |sizeof| sense) as other pointers, or even that +a pointer to a function of three arguments has the same size as a pointer to a +function of two. (Almost certainly it does: but the C99 standard is pretty clear +that you take your life into your own hands making these casual assumptions.) + +On the brighter side, though, modern C compilers are good at compiling switch +statements with easy-to-index case numbers in an efficient way: so what we cannot +legally express in source code will quite likely be what it compiles anyway, +and it is unlikely that |i7_gen_call| will be slow. + += +void CSFunctionModel::write_gen_call(code_generation *gen) { + segmentation_pos saved = CodeGen::select(gen, cs_function_callers_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("override public int i7_gen_call(\n"); + WRITE("Inform.Process proc, int id, int[] args) {\n"); INDENT; + WRITE("int rv = 0;\n"); + WRITE("switch (id) {\n"); INDENT; + WRITE("case 0: rv = 0; break;\n"); + vanilla_function *vf; + LOOP_OVER(vf, vanilla_function) { + WRITE("case "); + CSNamespace::mangle(NULL, OUT, vf->identifier); + WRITE(": "); + if (vf->takes_variable_arguments) @ + else @; + WRITE(" break;\n"); + } + WRITE("default: System.Console.WriteLine(\"function %%{0:D} not found\\n\", id); proc.i7_fatal_exit(); break;\n"); + OUTDENT; WRITE("}\n"); + WRITE("return rv;\n"); + OUTDENT; WRITE("}\n"); + CodeGen::deselect(gen, saved); +} + +@ = + WRITE("rv = "); + CSFunctionModel::CS_function_identifier(gen, OUT, vf); + WRITE("(/*gacp*/proc"); + for (int i=0; imax_arity; i++) WRITE(", args[%d]", i); + WRITE(");"); + +@ = + WRITE("for (int i=args.Length-1; i>=0; i--) proc.i7_push(args[i]); "); + WRITE("rv = "); + CSFunctionModel::CS_function_identifier(gen, OUT, vf); + WRITE("(/*gas*/proc, args.Length"); + for (int i=1; imax_arity; i++) WRITE(", 0"); + WRITE(");\n"); + +@h Declaring functions. +This is now straightforward except for one last annoying part of the Inter calling +convention. + +It is legal for an Inter function to leave pushed values still on the stack when +it exits. If the function takes varargs, those values allow for exotic forms of +return value, and are left there for the caller to deal with (or not). But if +it's a regular, non-varargs sort of function, then the stack must be restored +to the status quo ante when the function returns, just as if it had properly +pulled all the values it had pushed. + +We deal with this by having a "stack-safe" version of the function whose only +job is to save the stack pointer, call the "unsafe" (i.e. real) version of the +function, then restore the stack pointer again. + +The C types of the safe and unsafe functions are identical, so the following +look much like //CSFunctionModel::CS_function_identifier// and +//CSFunctionModel::CS_function_prototype//. + += +void CSFunctionModel::unsafe_CS_function_identifier(code_generation *gen, OUTPUT_STREAM, + vanilla_function *vf) { + CSNamespace::mangle_with(NULL, OUT, vf->identifier, I"ifn"); +} + +void CSFunctionModel::unsafe_CS_function_prototype(code_generation *gen, OUTPUT_STREAM, + vanilla_function *vf) { + WRITE("int "); + CSFunctionModel::unsafe_CS_function_identifier(gen, OUT, vf); + WRITE("(Inform.Process proc"); + text_stream *local_name; + LOOP_OVER_LINKED_LIST(local_name, text_stream, vf->locals) { + WRITE(", int "); + Generators::mangle(gen, OUT, local_name); + } + WRITE(")"); +} + +@ = +void CSFunctionModel::declare_function(code_generator *gtr, code_generation *gen, + vanilla_function *vf) { + text_stream *fn_name = vf->identifier; + segmentation_pos saved = CodeGen::select(gen, cs_function_declarations_I7CGS); + text_stream *OUT = CodeGen::current(gen); + @; + if (vf->takes_variable_arguments == FALSE) @; + CodeGen::deselect(gen, saved); +} + +@ = + if (vf->takes_variable_arguments) CSFunctionModel::CS_function_prototype(gen, OUT, vf); + else CSFunctionModel::unsafe_CS_function_prototype(gen, OUT, vf); + WRITE(" {\n"); + WRITE("proc.i7_debug_stack(\"%S\");\n", fn_name); + if (Str::eq(fn_name, I"DebugAction")) { + WRITE("switch (i7_mgl_local_a) {\n"); + text_stream *aname; + LOOP_OVER_LINKED_LIST(aname, text_stream, gen->actions) { + WRITE("case i7_ss_%S", aname); + WRITE(": printf(\"%S\"); return 1;\n", aname); + } + WRITE("}\n"); + } + CS_GEN_DATA(fndata.compiling_function) = TRUE; + Vanilla::node(gen, vf->function_body); + CS_GEN_DATA(fndata.compiling_function) = FALSE; + WRITE("return 1;\n"); + WRITE("\n}\n\n"); + +@ = + CSFunctionModel::CS_function_prototype(gen, OUT, vf); + WRITE(" {\n"); + WRITE("int ssp = proc.state.stack_pointer;\n"); + WRITE("int rv = "); + CSFunctionModel::unsafe_CS_function_identifier(gen, OUT, vf); + WRITE("(/*cssof*/proc"); + text_stream *local_name; + LOOP_OVER_LINKED_LIST(local_name, text_stream, vf->locals) { + WRITE(", "); + Generators::mangle(gen, OUT, local_name); + } + WRITE(");\n"); + WRITE("proc.state.stack_pointer = ssp;\n", vf->identifier); + WRITE("return rv;\n"); + WRITE("\n}\n\n"); + +@ = +int CSFunctionModel::inside_function(code_generation *gen) { + if (CS_GEN_DATA(fndata.compiling_function)) return TRUE; + return FALSE; +} + +@ Labels can be placed in C code with the notation |LabelName:|, but note that +in C it is a syntax error for a label to occur at the end of a block, e.g., +|while (1) { ...; EndOfLoop: }| is a syntax error. This can be put right with +an empty statement, i.e., a semicolon: |while (1) { ...; EndOfLoop: ; }| +And in case that is what we need here, we always place an empty statement after +a label. + += +void CSFunctionModel::place_label(code_generator *gtr, code_generation *gen, + text_stream *label_name) { + text_stream *OUT = CodeGen::current(gen); + LOOP_THROUGH_TEXT(pos, label_name) + if (Str::get(pos) != '.') + PUT(Str::get(pos)); + WRITE(": ;\n", label_name); +} + +@ Labels are not really "evaluated" in C: |goto| destinations are not values. +Evaluation in this sense just means compiling the name used a sort of argument +to the |goto| statement. + +Note that label names, whose scope is confined to the function in which they +occur, are unmangled. This is safe because label names have their own namespace +in C, so they cannot clash with other identifiers. + += +void CSFunctionModel::evaluate_label(code_generator *gtr, code_generation *gen, + text_stream *label_name) { + text_stream *OUT = CodeGen::current(gen); + LOOP_THROUGH_TEXT(pos, label_name) + if (Str::get(pos) != '.') + PUT(Str::get(pos)); +} + +@h Outward-bound function calls. +"Outward" here means "when a function compiled from Inter makes a call to a C +function from the world outside, i.e., which wasn't compiled from Inter". This +is done with the |!externalcall| primitive: see below. + +In order to make the linking work, we need to ensure that our code declares +the external function's name before use. But we should do this only for those +functions we actually need to call, and only once for each of them. So we keep +a dictionary of those already declared. + +Note that all external identifiers begin with |external__|, which is 10 characters +long. + += +text_stream *CSFunctionModel::ensure_external_function_predeclared(code_generation *gen, + text_stream *external_identifier) { + dictionary *D = CS_GEN_DATA(fndata.predeclared_external_functions); + text_stream *key = Str::new(); + for (int i=10; iidentifier, I"xfn"); +} + +void CSFunctionModel::inward_CS_function_prototype(code_generation *gen, OUTPUT_STREAM, + vanilla_function *vf) { + WRITE("int "); + CSFunctionModel::inward_CS_function_identifier(gen, OUT, vf); + WRITE("(Inform.Process proc"); + for (int i=0; iformal_arity; i++) WRITE(", int p%d", i); + WRITE(")"); +} + +@ = +void CSFunctionModel::write_inward_calling_wrappers(code_generation *gen) { + vanilla_function *vf; + LOOP_OVER(vf, vanilla_function) { + @; + @; + @; + } +} + +@ This goes into the main C file we are compiling. + +@ = + segmentation_pos saved = CodeGen::select(gen, cs_function_callers_I7CGS); + text_stream *OUT = CodeGen::current(gen); + CSFunctionModel::inward_CS_function_prototype(gen, OUT, vf); WRITE(" {\n"); INDENT; + WRITE("return "); + CSFunctionModel::CS_function_identifier(gen, OUT, vf); + WRITE("/*cwfic*/(proc"); + for (int i=0; imax_arity; i++) { + WRITE(", "); + if (i < vf->formal_arity) WRITE("p%d", i); else WRITE("0"); + } + WRITE(");\n"); + OUTDENT; WRITE("}\n"); + CodeGen::deselect(gen, saved); + +@ This goes into the secondary header file, and predeclares the wrapper function +for linking purposes. + +@ = + segmentation_pos saved = CodeGen::select(gen, cs_function_symbols_I7CGS); + text_stream *OUT = CodeGen::current(gen); + CSFunctionModel::inward_CS_function_prototype(gen, OUT, vf); WRITE(";\n"); + CodeGen::deselect(gen, saved); + +@ Similarly, this for the header file produces a nicer name which can be used +for the wrapper. + +@ = + TEMPORARY_TEXT(synopsis) + VanillaFunctions::syntax_synopsis(synopsis, vf); + TEMPORARY_TEXT(val) + CSFunctionModel::inward_CS_function_identifier(gen, val, vf); + segmentation_pos saved = CodeGen::select(gen, cs_function_symbols_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("/*mfawfn*/const int %S = %S;\n", CSTarget::symbols_header_identifier(gen, I"F", synopsis), val); + CodeGen::deselect(gen, saved); + DISCARD_TEXT(val) + DISCARD_TEXT(synopsis) + +@h Primitives for indirect or external function calls. +Most function calls are made explicitly: see //CSFunctionModel::invoke_function// +above. But the Inter primitives below offer a way to call functions whose identities +are not known at compile time, or which are not even part of the Inter program. + +The following primitives all simply call functions |i7_call_0|, and so on -- see +below for their definitions -- except for |!externalcall|. + += +int CSFunctionModel::invoke_primitive(code_generation *gen, inter_ti bip, inter_tree_node *P) { + text_stream *OUT = CodeGen::current(gen); + switch (bip) { + case INDIRECT0_BIP: case INDIRECT0V_BIP: + WRITE("proc.i7_call_0("); VNODE_1C; WRITE(")"); break; + case INDIRECT1_BIP: case INDIRECT1V_BIP: + WRITE("proc.i7_call_1("); VNODE_1C; WRITE(", "); + VNODE_2C; WRITE(")"); break; + case INDIRECT2_BIP: case INDIRECT2V_BIP: + WRITE("proc.i7_call_2("); VNODE_1C; WRITE(", "); + VNODE_2C; WRITE(", "); VNODE_3C; WRITE(")"); break; + case INDIRECT3_BIP: case INDIRECT3V_BIP: + WRITE("proc.i7_call_3("); VNODE_1C; WRITE(", "); + VNODE_2C; WRITE(", "); VNODE_3C; WRITE(", "); VNODE_4C; WRITE(")"); break; + case INDIRECT4_BIP: case INDIRECT4V_BIP: + WRITE("proc.i7_call_4("); VNODE_1C; WRITE(", "); + VNODE_2C; WRITE(", "); VNODE_3C; WRITE(", "); VNODE_4C; WRITE(", "); + VNODE_5C; WRITE(")"); break; + case INDIRECT5_BIP: case INDIRECT5V_BIP: + WRITE("proc.i7_call_5("); VNODE_1C; WRITE(", "); + VNODE_2C; WRITE(", "); VNODE_3C; WRITE(", "); VNODE_4C; WRITE(", "); + VNODE_5C; WRITE(", "); VNODE_6C; WRITE(")"); break; + case MESSAGE0_BIP: + WRITE("proc.i7_mcall_0("); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(")"); break; + case MESSAGE1_BIP: + WRITE("proc.i7_mcall_1("); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(", "); + VNODE_3C; WRITE(")"); break; + case MESSAGE2_BIP: + WRITE("proc.i7_mcall_2("); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(", "); + VNODE_3C; WRITE(", "); VNODE_4C; WRITE(")"); break; + case MESSAGE3_BIP: + WRITE("proc.i7_mcall_3("); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(", "); + VNODE_3C; WRITE(", "); VNODE_4C; WRITE(", "); VNODE_5C; WRITE(")"); break; + case EXTERNALCALL_BIP: + @; break; + default: + return NOT_APPLICABLE; + } + return FALSE; +} + +@ = + inter_tree_node *N = InterTree::first_child(P); + if (Inode::is(N, VAL_IST)) { + inter_pair val = ValInstruction::value(N); + if (InterValuePairs::is_text(val)) { + text_stream *text = InterValuePairs::to_text(gen->from, val); + WRITE("%S/*gpe*/(proc, ", + CSFunctionModel::ensure_external_function_predeclared(gen, text)); + VNODE_2C; WRITE(")"); + } else { + internal_error("unimplemented form of !externalcall"); + } + } else { + internal_error("unimplemented form of !externalcall"); + } + +@ The following functions implement the above. |i7_call_N| provides a general +way to call an Inter function with |N| arguments, up to 5. + +But these are not really different from each other: they all simply call |i7_gen_call|, +the function we compiled laboriously in //CSFunctionModel::write_gen_call// above, +to do the actual business. + += (text to inform7_cslib.cs) +partial class Process { + public int i7_call_0(int id) { + int[] args = new int[10]; + return i7_gen_call(id, args); + } + public int i7_call_1(int id, int v) { + int[] args = new int[10]; + args[0] = v; + return i7_gen_call(id, args); + } + public int i7_call_2(int id, int v, int v2) { + int[] args = new int[10]; + args[0] = v; args[1] = v2; + return i7_gen_call(id, args); + } + public int i7_call_3(int id, int v, int v2, + int v3) { + int[] args = new int[10]; + args[0] = v; args[1] = v2; args[2] = v3; + return i7_gen_call(id, args); + } + public int i7_call_4(int id, int v, int v2, + int v3, int v4) { + int[] args = new int[10]; + args[0] = v; args[1] = v2; args[2] = v3; args[3] = v4; + return i7_gen_call(id, args); + } + public int i7_call_5(int id, int v, int v2, + int v3, int v4, int v5) { + int[] args = new int[10]; + args[0] = v; args[1] = v2; args[2] = v3; args[3] = v4; args[4] = v5; + return i7_gen_call(id, args); + } +} += + +@ The following functions implement the above. |i7_mcall_N| provides a general +way to make a "message call" to an Inter function with |N| arguments, up to 3. +Message calls are really the same as regular function calls, except that the +function ID is read from a property of an object, and except that the |self| +variable has to be set to that object when the function is running (and restored +back to its previous value afterwards). Again, we use |i7_gen_call| to do the +actual work. + += (text to inform7_cslib.cs) +partial class Story { + protected internal int i7_var_self; +} + +partial class Process { + int i7_mcall_0(int to, int prop) { + int[] args = new int[0]; + int saved = state.variables[story.i7_var_self]; + state.variables[story.i7_var_self] = to; + int id = i7_read_prop_value(to, prop); + int rv = i7_gen_call(id, args); + state.variables[story.i7_var_self] = saved; + return rv; + } + + int i7_mcall_1(int to, int prop, int v) { + int[] args = new int[1]; + args[0] = v; + int saved = state.variables[story.i7_var_self]; + state.variables[story.i7_var_self] = to; + int id = i7_read_prop_value(to, prop); + int rv = i7_gen_call(id, args); + state.variables[story.i7_var_self] = saved; + return rv; + } + + int i7_mcall_2(int to, int prop, int v, + int v2) { + int[] args = new int[2]; + args[0] = v; args[1] = v2; + int saved = state.variables[story.i7_var_self]; + state.variables[story.i7_var_self] = to; + int id = i7_read_prop_value(to, prop); + int rv = i7_gen_call(id, args); + state.variables[story.i7_var_self] = saved; + return rv; + } + + int i7_mcall_3(int to, int prop, int v, + int v2, int v3) { + int[] args = new int[3]; + args[0] = v; args[1] = v2; args[2] = v3; + int saved = state.variables[story.i7_var_self]; + state.variables[story.i7_var_self] = to; + int id = i7_read_prop_value(to, prop); + int rv = i7_gen_call(id, args); + state.variables[story.i7_var_self] = saved; + return rv; + } +} += diff --git a/inter/final-module/Chapter 6/C# Global Variables.w b/inter/final-module/Chapter 6/C# Global Variables.w new file mode 100644 index 0000000000..6c67a33f02 --- /dev/null +++ b/inter/final-module/Chapter 6/C# Global Variables.w @@ -0,0 +1,151 @@ +[CSGlobals::] C# Global Variables. + +Global variables translated to C#. + +@ = +void CSGlobals::initialise(code_generator *gtr) { + METHOD_ADD(gtr, DECLARE_VARIABLES_MTID, CSGlobals::declare_variables); + METHOD_ADD(gtr, EVALUATE_VARIABLE_MTID, CSGlobals::evaluate_variable); +} + +typedef struct CS_generation_variables_data { + int no_variables; +} CS_generation_variables_data; + +void CSGlobals::initialise_data(code_generation *gen) { + CS_GEN_DATA(vardata.no_variables) = 1; +} + +@ The basic scheme is this: the global Inter variables are going to have +their values stored in an array, so to identify which variable you are reading +or writing, you need an index (i.e., position) in that array. + +The main thing we need to compile is a (static) array of initial values for +these variables, so that a new process can be initialised. But we must also +define constants to refer to their positions in the array. + += (text to inform7_cslib.cs) +partial class Story { + protected internal int i7_no_variables; + protected internal int[] i7_initial_variable_values; +} += + += +void CSGlobals::begin(code_generation *gen) { +} + +void CSGlobals::end(code_generation *gen) { + segmentation_pos saved = CodeGen::select(gen, cs_constructor_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("i7_no_variables = %d;\n", CS_GEN_DATA(vardata.no_variables)); + CodeGen::deselect(gen, saved); +} + +@ We will assign the global variables unique index numbers 0, 1, 2, ..., with +the special variable |self| given index 0. Note that |self| always exists, +but has no Inter declaration node. + += +void CSGlobals::declare_variables(code_generator *gtr, code_generation *gen, linked_list *L) { + segmentation_pos saved = CodeGen::select(gen, cs_constructor_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("i7_initial_variable_values = new[] {\n"); + + @; + @; + @; + + int N = 1; + inter_symbol *var_name; + LOOP_OVER_LINKED_LIST(var_name, inter_symbol, L) { + text_stream *identifier = InterSymbol::trans(var_name); + @; + @; + @; + N++; + } + CS_GEN_DATA(vardata.no_variables) = N; + + WRITE("};\n"); + CodeGen::deselect(gen, saved); +} + +@ = + /* segmentation_pos saved = CodeGen::select(gen, cs_constructor_I7CGS); + text_stream *OUT = CodeGen::current(gen); + CSNamespace::mangle_variable(OUT, I"self"); + WRITE("= 0;\n"); + CodeGen::deselect(gen, saved); */ + +@ = + segmentation_pos saved = CodeGen::select(gen, cs_predeclarations_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("/*dcpga*/const int "); + CSNamespace::mangle_variable(OUT, identifier); + WRITE(" = %d;\n", N); + CodeGen::deselect(gen, saved); + +@ = + segmentation_pos saved = CodeGen::select(gen, cs_constructor_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE(" 0 /* self */\n"); + CodeGen::deselect(gen, saved); + +@ = + segmentation_pos saved = CodeGen::select(gen, cs_constructor_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE(", "); + if (var_name->definition) { + inter_tree_node *P = var_name->definition; + CodeGen::pair(gen, P, VariableInstruction::value(P)); + } else { + WRITE("0"); + } + WRITE(" /* %S */\n", identifier); + CodeGen::deselect(gen, saved); + +@ = + CSGlobals::define_header_constant_for_variable(gen, I"self", 0); + +@ = + text_stream *name = Metadata::optional_textual( + InterPackage::container(var_name->definition), I"^name"); + if (name) + CSGlobals::define_header_constant_for_variable(gen, name, N); + else + CSGlobals::define_header_constant_for_variable(gen, identifier, N); + +@ = +void CSGlobals::define_header_constant_for_variable(code_generation *gen, text_stream *var_name, + int id) { + segmentation_pos saved = CodeGen::select(gen, cs_variable_symbols_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("/*dhcv*/const int %S = %d;\n", CSTarget::symbols_header_identifier(gen, I"V", var_name), id); + CodeGen::deselect(gen, saved); +} + +@ Within a process |proc|, the current value of variable |i| is |proc.state.variables[i]|. + += +void CSGlobals::evaluate_variable(code_generator *gtr, code_generation *gen, + inter_symbol *var_name, int as_reference) { + text_stream *OUT = CodeGen::current(gen); + WRITE("proc.state.variables["); + CSNamespace::mangle_variable(OUT, InterSymbol::trans(var_name)); + WRITE("]"); +} + +@ Finally, this function, part of the C# library, initialises the variables for a +newly-starting process. + += (text to inform7_cslib.cs) +partial class Process { + void i7_initialise_variables() { + // TODO: use array copy method instead + state.variables = new int[story.i7_no_variables]; + for (int i=0; i 0; j--) proc.i7_print_char(32);"); break; + case FONT_BIP: + WRITE("proc.i7_styling(1, "); VNODE_1C; WRITE(")"); break; + case STYLE_BIP: + WRITE("proc.i7_styling(2, "); VNODE_1C; WRITE(")"); break; + case PRINT_BIP: + WRITE("proc.i7_print_CLR_string("); + CodeGen::lt_mode(gen, PRINTING_LTM); VNODE_1C; + CodeGen::lt_mode(gen, REGULAR_LTM); WRITE(")"); break; + case PRINTCHAR_BIP: + WRITE("proc.i7_print_char("); VNODE_1C; WRITE(")"); break; + case PRINTNL_BIP: + WRITE("proc.i7_print_char('\\n')"); break; + case PRINTOBJ_BIP: + WRITE("proc.i7_print_object("); VNODE_1C; WRITE(")"); break; + case PRINTNUMBER_BIP: + WRITE("proc.i7_print_decimal("); VNODE_1C; WRITE(")"); break; + case PRINTSTRING_BIP: + WRITE("proc.i7_print_CLR_string(proc.i7_text_to_CLR_string("); VNODE_1C; + WRITE("))"); break; + case PRINTDWORD_BIP: + WRITE("proc.i7_print_dword("); VNODE_1C; WRITE(")"); break; + case BOX_BIP: + WRITE("proc.i7_print_box("); CodeGen::lt_mode(gen, BOX_LTM); VNODE_1C; + CodeGen::lt_mode(gen, REGULAR_LTM); WRITE(")"); break; + case ENABLEPRINTING_BIP: + WRITE("{ int window_id;\n"); + WRITE("proc.i7_opcode_setiosys(2, 0); // Set to use Glk\n"); + WRITE("proc.i7_push(201); // = GG_MAINWIN_ROCK;\n"); + WRITE("proc.i7_push(3); // = wintype_TextBuffer;\n"); + WRITE("proc.i7_push(0);\n"); + WRITE("proc.i7_push(0);\n"); + WRITE("proc.i7_push(0);\n"); + WRITE("proc.i7_opcode_glk(35, 5, out window_id); // glk_window_open, pushing a window ID\n"); + WRITE("proc.i7_push(window_id);\n"); + WRITE("proc.i7_opcode_glk(47, 1, out int _); // glk_set_window to that window ID\n"); + WRITE("}\n"); + break; + default: return NOT_APPLICABLE; + } + return FALSE; +} + +@ See //C# Literals// for the implementation of |i7_print_dword|: it funnels +through to |i7_print_char|, and so do all of these: + + += (text to inform7_cslib.cs) +partial class Process { + public void i7_print_CLR_string(string clr_string) { + if (clr_string != null) + for (int i=0; i < clr_string.Length; i++) + i7_print_char((int) clr_string[i]); + } + + public void i7_print_decimal(int x) { + i7_print_CLR_string(x.ToString()); + } + + public void i7_print_object(int x) { + i7_print_decimal(x); + } + + public void i7_print_box(int x) { + Console.WriteLine("Unimplemented: i7_print_box."); + i7_fatal_exit(); + } += + +@ Which in turn uses the |@glk| opcode: += (text to inform7_cslib.cs) + internal void i7_print_char(int x) { + if (x == 13) x = 10; + i7_push(x); + int current = 0; + i7_opcode_glk(GlkOpcodes.i7_glk_stream_get_current, 0, out current); + i7_push(current); + i7_opcode_glk(GlkOpcodes.i7_glk_put_char_stream, 2, out int _); + } += + +@ At this point, then, all of our I/O needs will be handled if we can just +define two functions: |i7_styling|, for setting the font style, and |i7_opcode_glk|. +So we're nearly done, right? Right? + +But in fact we route both of these functions through hooks which the user can +provide, so that the user can change the entire I/O model (if she is willing to +code up an alternative): + += (text to inform7_cslib.cs) + internal void i7_styling(int which, int what) { + stylist(this, which, what); + } + internal void i7_opcode_glk(int glk_api_selector, int varargc, + out int z) { + z = glk_implementation(this, glk_api_selector, varargc); + } +} += + +@ What makes this more burdensome is that |@glk| is not so much a single opcode +as an entire instruction set: it is an compendium of over 120 disparate operations. +Indeed, the |glk_api_selector| argument to |i7_opcode_glk| chooses which one is +being used. For convenience, we define a set of names for them all -- which does +not imply any commitment to implement them all. + += (text to inform7_cslib.cs) +public static class GlkOpcodes { + public const int i7_glk_exit = 0x0001; + public const int i7_glk_set_interrupt_handler = 0x0002; + public const int i7_glk_tick = 0x0003; + public const int i7_glk_gestalt = 0x0004; + public const int i7_glk_gestalt_ext = 0x0005; + public const int i7_glk_window_iterate = 0x0020; + public const int i7_glk_window_get_rock = 0x0021; + public const int i7_glk_window_get_root = 0x0022; + public const int i7_glk_window_open = 0x0023; + public const int i7_glk_window_close = 0x0024; + public const int i7_glk_window_get_size = 0x0025; + public const int i7_glk_window_set_arrangement = 0x0026; + public const int i7_glk_window_get_arrangement = 0x0027; + public const int i7_glk_window_get_type = 0x0028; + public const int i7_glk_window_get_parent = 0x0029; + public const int i7_glk_window_clear = 0x002A; + public const int i7_glk_window_move_cursor = 0x002B; + public const int i7_glk_window_get_stream = 0x002C; + public const int i7_glk_window_set_echo_stream = 0x002D; + public const int i7_glk_window_get_echo_stream = 0x002E; + public const int i7_glk_set_window = 0x002F; + public const int i7_glk_window_get_sibling = 0x0030; + public const int i7_glk_stream_iterate = 0x0040; + public const int i7_glk_stream_get_rock = 0x0041; + public const int i7_glk_stream_open_file = 0x0042; + public const int i7_glk_stream_open_memory = 0x0043; + public const int i7_glk_stream_close = 0x0044; + public const int i7_glk_stream_set_position = 0x0045; + public const int i7_glk_stream_get_position = 0x0046; + public const int i7_glk_stream_set_current = 0x0047; + public const int i7_glk_stream_get_current = 0x0048; + public const int i7_glk_stream_open_resource = 0x0049; + public const int i7_glk_fileref_create_temp = 0x0060; + public const int i7_glk_fileref_create_by_name = 0x0061; + public const int i7_glk_fileref_create_by_prompt = 0x0062; + public const int i7_glk_fileref_destroy = 0x0063; + public const int i7_glk_fileref_iterate = 0x0064; + public const int i7_glk_fileref_get_rock = 0x0065; + public const int i7_glk_fileref_delete_file = 0x0066; + public const int i7_glk_fileref_does_file_exist = 0x0067; + public const int i7_glk_fileref_create_from_fileref = 0x0068; + public const int i7_glk_put_char = 0x0080; + public const int i7_glk_put_char_stream = 0x0081; + public const int i7_glk_put_string = 0x0082; + public const int i7_glk_put_string_stream = 0x0083; + public const int i7_glk_put_buffer = 0x0084; + public const int i7_glk_put_buffer_stream = 0x0085; + public const int i7_glk_set_style = 0x0086; + public const int i7_glk_set_style_stream = 0x0087; + public const int i7_glk_get_char_stream = 0x0090; + public const int i7_glk_get_line_stream = 0x0091; + public const int i7_glk_get_buffer_stream = 0x0092; + public const int i7_glk_char_to_lower = 0x00A0; + public const int i7_glk_char_to_upper = 0x00A1; + public const int i7_glk_stylehint_set = 0x00B0; + public const int i7_glk_stylehint_clear = 0x00B1; + public const int i7_glk_style_distinguish = 0x00B2; + public const int i7_glk_style_measure = 0x00B3; + public const int i7_glk_select = 0x00C0; + public const int i7_glk_select_poll = 0x00C1; + public const int i7_glk_request_line_event = 0x00D0; + public const int i7_glk_cancel_line_event = 0x00D1; + public const int i7_glk_request_char_event = 0x00D2; + public const int i7_glk_cancel_char_event = 0x00D3; + public const int i7_glk_request_mouse_event = 0x00D4; + public const int i7_glk_cancel_mouse_event = 0x00D5; + public const int i7_glk_request_timer_events = 0x00D6; + public const int i7_glk_image_get_info = 0x00E0; + public const int i7_glk_image_draw = 0x00E1; + public const int i7_glk_image_draw_scaled = 0x00E2; + public const int i7_glk_window_flow_break = 0x00E8; + public const int i7_glk_window_erase_rect = 0x00E9; + public const int i7_glk_window_fill_rect = 0x00EA; + public const int i7_glk_window_set_background_color = 0x00EB; + public const int i7_glk_schannel_iterate = 0x00F0; + public const int i7_glk_schannel_get_rock = 0x00F1; + public const int i7_glk_schannel_create = 0x00F2; + public const int i7_glk_schannel_destroy = 0x00F3; + public const int i7_glk_schannel_create_ext = 0x00F4; + public const int i7_glk_schannel_play_multi = 0x00F7; + public const int i7_glk_schannel_play = 0x00F8; + public const int i7_glk_schannel_play_ext = 0x00F9; + public const int i7_glk_schannel_stop = 0x00FA; + public const int i7_glk_schannel_set_volume = 0x00FB; + public const int i7_glk_sound_load_hint = 0x00FC; + public const int i7_glk_schannel_set_volume_ext = 0x00FD; + public const int i7_glk_schannel_pause = 0x00FE; + public const int i7_glk_schannel_unpause = 0x00FF; + public const int i7_glk_set_hyperlink = 0x0100; + public const int i7_glk_set_hyperlink_stream = 0x0101; + public const int i7_glk_request_hyperlink_event = 0x0102; + public const int i7_glk_cancel_hyperlink_event = 0x0103; + public const int i7_glk_buffer_to_lower_case_uni = 0x0120; + public const int i7_glk_buffer_to_upper_case_uni = 0x0121; + public const int i7_glk_buffer_to_title_case_uni = 0x0122; + public const int i7_glk_buffer_canon_decompose_uni = 0x0123; + public const int i7_glk_buffer_canon_normalize_uni = 0x0124; + public const int i7_glk_put_char_uni = 0x0128; + public const int i7_glk_put_string_uni = 0x0129; + public const int i7_glk_put_buffer_uni = 0x012A; + public const int i7_glk_put_char_stream_uni = 0x012B; + public const int i7_glk_put_string_stream_uni = 0x012C; + public const int i7_glk_put_buffer_stream_uni = 0x012D; + public const int i7_glk_get_char_stream_uni = 0x0130; + public const int i7_glk_get_buffer_stream_uni = 0x0131; + public const int i7_glk_get_line_stream_uni = 0x0132; + public const int i7_glk_stream_open_file_uni = 0x0138; + public const int i7_glk_stream_open_memory_uni = 0x0139; + public const int i7_glk_stream_open_resource_uni = 0x013A; + public const int i7_glk_request_char_event_uni = 0x0140; + public const int i7_glk_request_line_event_uni = 0x0141; + public const int i7_glk_set_echo_line_event = 0x0150; + public const int i7_glk_set_terminators_line_event = 0x0151; + public const int i7_glk_current_time = 0x0160; + public const int i7_glk_current_simple_time = 0x0161; + public const int i7_glk_time_to_date_utc = 0x0168; + public const int i7_glk_time_to_date_local = 0x0169; + public const int i7_glk_simple_time_to_date_utc = 0x016A; + public const int i7_glk_simple_time_to_date_local = 0x016B; + public const int i7_glk_date_to_time_utc = 0x016C; + public const int i7_glk_date_to_time_local = 0x016D; + public const int i7_glk_date_to_simple_time_utc = 0x016E; + public const int i7_glk_date_to_simple_time_local = 0x016F; +} += + +A few other constants will also be useful. These are the window IDs for the +three Glk windows used by the standard Inform 7 kits: |I7_BODY_TEXT_ID| is +where text is regularly printed; |I7_STATUS_TEXT_ID| is for the "status line" +at the top of a traditional interactive fiction display, but can simply be +ignored for non-IF purposes; and |I7_BOX_TEXT_ID| is where box quotations +would be displayed over the top of text, though C projects probably should +not use this, and the default Glk implementation here ignores it. + += (text to inform7_cslib.cs) +partial class Process { + public const int I7_BODY_TEXT_ID = 201; + public const int I7_STATUS_TEXT_ID = 202; + public const int I7_BOX_TEXT_ID = 203; += + +These are needed for different forms of file I/O: + += (text to inform7_cslib.cs) + public const int i7_fileusage_Data = 0x00; + public const int i7_fileusage_SavedGame = 0x01; + public const int i7_fileusage_Transcript = 0x02; + public const int i7_fileusage_InputRecord = 0x03; + public const int i7_fileusage_TypeMask = 0x0f; + public const int i7_fileusage_TextMode = 0x100; + public const int i7_fileusage_BinaryMode = 0x000; + + public const int i7_filemode_Write = 0x01; + public const int i7_filemode_Read = 0x02; + public const int i7_filemode_ReadWrite = 0x03; + public const int i7_filemode_WriteAppend = 0x05; += + +And these are modes for seeking a position in a file: + += (text to inform7_cslib.cs) + public const int i7_seekmode_Start = (0); + public const int i7_seekmode_Current = (1); + public const int i7_seekmode_End = (2); += + +And these are "event types": + += (text to inform7_cslib.cs) + public const int i7_evtype_None = 0; + public const int i7_evtype_Timer = 1; + public const int i7_evtype_CharInput = 2; + public const int i7_evtype_LineInput = 3; + public const int i7_evtype_MouseInput = 4; + public const int i7_evtype_Arrange = 5; + public const int i7_evtype_Redraw = 6; + public const int i7_evtype_SoundNotify = 7; + public const int i7_evtype_Hyperlink = 8; + public const int i7_evtype_VolumeNotify = 9; +} += + +Finally, these are the gestalt values: that is, the selection of bells and +whistles which a Glk implementation can offer -- + += (text to inform7_cslib.cs) +public static class GlkGestalts { + public const int i7_gestalt_Version = 0; + public const int i7_gestalt_CharInput = 1; + public const int i7_gestalt_LineInput = 2; + public const int i7_gestalt_CharOutput = 3; + public const int i7_gestalt_CharOutput_ApproxPrint = 1; + public const int i7_gestalt_CharOutput_CannotPrint = 0; + public const int i7_gestalt_CharOutput_ExactPrint = 2; + public const int i7_gestalt_MouseInput = 4; + public const int i7_gestalt_Timer = 5; + public const int i7_gestalt_Graphics = 6; + public const int i7_gestalt_DrawImage = 7; + public const int i7_gestalt_Sound = 8; + public const int i7_gestalt_SoundVolume = 9; + public const int i7_gestalt_SoundNotify = 10; + public const int i7_gestalt_Hyperlinks = 11; + public const int i7_gestalt_HyperlinkInput = 12; + public const int i7_gestalt_SoundMusic = 13; + public const int i7_gestalt_GraphicsTransparency = 14; + public const int i7_gestalt_Unicode = 15; + public const int i7_gestalt_UnicodeNorm = 16; + public const int i7_gestalt_LineInputEcho = 17; + public const int i7_gestalt_LineTerminators = 18; + public const int i7_gestalt_LineTerminatorKey = 19; + public const int i7_gestalt_DateTime = 20; + public const int i7_gestalt_Sound2 = 21; + public const int i7_gestalt_ResourceStream = 22; + public const int i7_gestalt_GraphicsCharInput = 23; +} \ No newline at end of file diff --git a/inter/final-module/Chapter 6/C# Literals.w b/inter/final-module/Chapter 6/C# Literals.w new file mode 100644 index 0000000000..d2366ade2f --- /dev/null +++ b/inter/final-module/Chapter 6/C# Literals.w @@ -0,0 +1,283 @@ +[CSLiteralsModel::] C# Literals. + +Text and dictionary words translated to C#. + +@h Introduction. +We take the word "literal" broadly rather than, well, literally: we include +under this heading a variety of ingredients of expressions which can legally be +used as constants. + += +void CSLiteralsModel::initialise(code_generator *gtr) { + METHOD_ADD(gtr, COMPILE_DICTIONARY_WORD_MTID, CSLiteralsModel::compile_dictionary_word); + METHOD_ADD(gtr, COMPILE_LITERAL_NUMBER_MTID, CSLiteralsModel::compile_literal_number); + METHOD_ADD(gtr, COMPILE_LITERAL_REAL_MTID, CSLiteralsModel::compile_literal_real); + METHOD_ADD(gtr, COMPILE_LITERAL_TEXT_MTID, CSLiteralsModel::compile_literal_text); + METHOD_ADD(gtr, COMPILE_LITERAL_SYMBOL_MTID, CSLiteralsModel::compile_literal_symbol); + METHOD_ADD(gtr, NEW_ACTION_MTID, CSLiteralsModel::new_action); +} + +typedef struct CS_generation_literals_model_data { + int text_count; + struct linked_list *texts; /* of |text_stream| */ +} CS_generation_literals_model_data; + +void CSLiteralsModel::initialise_data(code_generation *gen) { + CS_GEN_DATA(litdata.text_count) = 0; + CS_GEN_DATA(litdata.texts) = NEW_LINKED_LIST(text_stream); +} + +void CSLiteralsModel::begin(code_generation *gen) { + CSLiteralsModel::initialise_data(gen); + CSLiteralsModel::begin_text(gen); +} + +void CSLiteralsModel::end(code_generation *gen) { + CSLiteralsModel::end_text(gen); +} + +@h Symbols. +The following function expresses that a named constant can be used as a value in C# +just by naming it. That seems too obvious to need a function, but one can imagine +languages where it is not true. + += +void CSLiteralsModel::compile_literal_symbol(code_generator *gtr, code_generation *gen, + inter_symbol *aliased) { + text_stream *OUT = CodeGen::current(gen); + text_stream *S = InterSymbol::trans(aliased); + Generators::mangle(gen, OUT, S); +} + +@h Integers. +This is simple for once. A generator is not obliged to take the |hex_mode| hint +and show the number in hex in the code it generates; functionally, decimal would +be just as good. But since we can easily do so, why not. + += +void CSLiteralsModel::compile_literal_number(code_generator *gtr, + code_generation *gen, inter_ti val, int hex_mode) { + text_stream *OUT = CodeGen::current(gen); + if (hex_mode) WRITE("0x%x", val); + else WRITE("%d", val); +} + +@h Real numbers. +This is not at all simple, but the helpful //VanillaConstants::textual_real_to_uint32// +does all the work for us. + += +void CSLiteralsModel::compile_literal_real(code_generator *gtr, + code_generation *gen, text_stream *textual) { + uint32_t n = VanillaConstants::textual_real_to_uint32(textual); + text_stream *OUT = CodeGen::current(gen); + WRITE("(int) 0x%08x", n); +} + +@h Texts. +These are sometimes being used in |inv !print| or |inv !box|, in which case they +are never needed as values -- they're just printed. If that's the case, we +render directly as a double-quoted C text literal. + +Otherwise, we are in |REGULAR_LTM| mode. In that case, a text must be represented +by a value which is "of the class String", meaning, a value in a range which +begins at the constant |I7VAL_STRINGS_BASE|; subject to that requirement, we +have freedom to do more or less what we like, but we will make the smallest +range of String values possible. Each text will have a unique ID number counting +upwards from |I7VAL_STRINGS_BASE|. The actual text this represents will be an +entry in the |i7_texts| array, which can be accessed using the +|i7_text_to_CLR_string| method. + +(This is in contrast to the Inform 6 situation, where texts are represented by +addresses of compressed text in memory, so that the values are not consecutive +and the range they spread out over can be very large.) + += (text to inform7_cslib.cs) +partial class Story { + internal int i7_strings_base; +} + +partial class Process { + string[] i7_texts; + public string i7_text_to_CLR_string(int str) { + return i7_texts[str - story.i7_strings_base]; + } +} += + +The |i7_texts| array is written one entry at a time as we go along, and is +started here: + += +void CSLiteralsModel::begin_text(code_generation *gen) { +} + +void CSLiteralsModel::compile_literal_text(code_generator *gtr, code_generation *gen, + text_stream *S, int no_special_characters) { + text_stream *OUT = CodeGen::current(gen); + if (gen->literal_text_mode == REGULAR_LTM) { + WRITE("(I7VAL_STRINGS_BASE + %d)", CS_GEN_DATA(litdata.text_count)++); + text_stream *OUT = Str::new(); + @; + ADD_TO_LINKED_LIST(OUT, text_stream, CS_GEN_DATA(litdata.texts)); + } else { + @; + } +} + +@ = + WRITE("\""); + if (no_special_characters) @ + else @; + WRITE("\""); + +@ Tabs become spaces, but there shouldn't be any tabs here anyway; |NEWLINE_IN_STRING| +characters become actual newlines, which is what they mean anyway. Otherwise, though, +this simply prints out the text in a form which a C compiler will accept between +double-quotes. + +@ = + LOOP_THROUGH_TEXT(pos, S) { + inchar32_t c = Str::get(pos); + switch(c) { + case '"': WRITE("\\\""); break; + case '\\': WRITE("\\\\"); break; + case '\t': WRITE(" "); break; + case '\n': WRITE("\\n"); break; + case NEWLINE_IN_STRING: WRITE("\\n"); break; + default: PUT(c); break; + } + } + +@ All of that is true here too, but we also convert the traditional Inform 6 +notations for |@dd...| or |@{hh...}| giving character literals in decimal or +hex, and |~| for a double-quote, and |^| for a newline. + +@ = + for (int i=0; i= '0') && (c <= '9')) return c - '0'; + if ((c >= 'a') && (c <= 'f')) return c - 'a' + 10; + if ((c >= 'A') && (c <= 'F')) return c - 'A' + 10; + return 0; +} + +@ At the end of the run, when there can be no further texts, we must close +the |i7_texts| array: + += +void CSLiteralsModel::end_text(code_generation *gen) { + segmentation_pos saved = CodeGen::select(gen, cs_quoted_text_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("const int i7_mgl_Grammar__Version = 2;\n"); + WRITE("string[] i7_texts = {\n"); + text_stream *T; + LOOP_OVER_LINKED_LIST(T, text_stream, CS_GEN_DATA(litdata.texts)) + WRITE("%S, ", T); + WRITE("\"\" };\n"); + CodeGen::deselect(gen, saved); +} + +int CSLiteralsModel::size_of_String_area(code_generation *gen) { + return CS_GEN_DATA(litdata.text_count); +} + +@h Action names. +These are used when processing changes to the model world in interactive fiction; +they do not exist in Basic Inform programs. + +True actions count upwards from 0; fake actions independently count upwards +from 4096. These are defined just as constants, with mangled names: + += +void CSLiteralsModel::new_action(code_generator *gtr, code_generation *gen, + text_stream *name, int true_action, int N) { + if (true_action) { + segmentation_pos saved = CodeGen::select(gen, cs_actions_symbols_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("/*na*/const int %S = %d;\n", CSTarget::symbols_header_identifier(gen, I"A", name), N); + CodeGen::deselect(gen, saved); + } + TEMPORARY_TEXT(O) + TEMPORARY_TEXT(M) + WRITE_TO(O, "##%S", name); + CSNamespace::mangle(gtr, M, O); + + segmentation_pos saved = CodeGen::select(gen, cs_actions_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("const int %S = %d;\n", M, N); + CodeGen::deselect(gen, saved); + + DISCARD_TEXT(O) + DISCARD_TEXT(M) +} + +@h Dictionary words. +These are used when parsing command grammar in interactive fiction; they do not +exist in Basic Inform programs. + +At runtime, dictionary words are addresses of small fixed-size arrays, and we +have very little flexibility about this because code in CommandParserKit makes +many assumptions about these arrays. So we will closely imitate what the Inform 6 +compiler would automatically do. + +In the array |DW|, the words |DW->1| to |DW->9| are the characters of the word, +with trailing nulls padding it out if the word is shorter than that. If it's +longer, then the text is truncated to 9 characters only. This means printing +out the text of a dictionary word is a somewhat faithless operation.[1] Still, +Inter provides a primitive to do that, and here is the implementation. + +[1] It would get every word in this footnote right except for dictionary, which +would print as dictionar. + += (text to inform7_cslib.cs) +partial class Process { + public void i7_print_dword(int at) { + for (byte i=1; i<=i7_mgl_DICT_WORD_SIZE; i++) { + int c = i7_read_word(at, i); + if (c == 0) break; + i7_print_char(c); + } + } +} += + +@ We will use the convenient Vanilla mechanism for compiling dictionary words, +so there is very little to do: + += +void CSLiteralsModel::compile_dictionary_word(code_generator *gtr, code_generation *gen, + text_stream *S, int pluralise) { + text_stream *OUT = CodeGen::current(gen); + vanilla_dword *dw = VanillaIF::text_to_noun_dword(gen, S, pluralise); + CSNamespace::mangle(gtr, OUT, dw->identifier); +} diff --git a/inter/final-module/Chapter 6/C# Memory Model.w b/inter/final-module/Chapter 6/C# Memory Model.w new file mode 100644 index 0000000000..54fe34c7dc --- /dev/null +++ b/inter/final-module/Chapter 6/C# Memory Model.w @@ -0,0 +1,541 @@ +[CSMemoryModel::] C# Memory Model. + +How arrays of all kinds are stored in C#. + +@h Setting up the model. +The Inter semantics require that there be an area of byte-accessible memory: + +(a) Byte-accessible memory must contain all of the arrays. These can but need +not have alignment gaps in between them. (For C, they do not.) +(b) "Addresses" in this memory identify individual byte positions in it. These +can but need not start at 0. (For C, they do.) They must not be too large to +fit into an Inter value. +(c) When an array name is compiled, its runtime value must be its address. +(d) When an Inter value is stored in byte-accessible memory, it occupies either +2 or 4 consecutive bytes, with the little end first. The result is called a +"word". (For C, always 4, which is always |sizeof(int)|.) Conversion between +a word stored in memory and an Inter value must be faithful in both directions. +(e) Words can be stored at any byte position, and not only at (say) multiples +of 2 or 4. +(f) Arrays in memory are free to contain a mixture of bytes and words: some do. +(g) Data may be written in byte form and read back in word form, or vice versa. + += +void CSMemoryModel::initialise(code_generator *gtr) { + METHOD_ADD(gtr, WORD_TO_BYTE_MTID, CSMemoryModel::word_to_byte); + METHOD_ADD(gtr, BEGIN_ARRAY_MTID, CSMemoryModel::begin_array); + METHOD_ADD(gtr, ARRAY_ENTRY_MTID, CSMemoryModel::array_entry); + METHOD_ADD(gtr, END_ARRAY_MTID, CSMemoryModel::end_array); +} + +typedef struct CS_generation_memory_model_data { + int himem; /* 1 more than the largest legal byte address */ + struct text_stream *array_name; /* of the array currently being compiled */ + int entry_count; /* within the array currently being compiled */ +} CS_generation_memory_model_data; + +void CSMemoryModel::initialise_data(code_generation *gen) { + CS_GEN_DATA(memdata.himem) = 0; + CS_GEN_DATA(memdata.array_name) = Str::new(); + CS_GEN_DATA(memdata.entry_count) = 0; +} + +@ For a given process |proc|, the current contents of byte-addressable memory will +be an array called |proc.state.memory|; here, we will compile a single static array +|i7_initial_memory| holding the initial contents of this memory, so that a new +process can be initialised from that. + +The first 64 bytes of memory are reserved for the "header". We don't write those +here, and instead blank them out to all 0s. + += +void CSMemoryModel::begin(code_generation *gen) { + segmentation_pos saved = CodeGen::select(gen, cs_memory_array_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("private void i7_set_initial_memory() {\n"); + INDENT; + WRITE("i7_initial_memory = new byte[] {\n"); + for (int i=0; i<64; i++) WRITE("0, "); WRITE("/* header */\n"); + CS_GEN_DATA(memdata.himem) += 64; + CodeGen::deselect(gen, saved); +} + +@ And we must close that array declaration, too: + += (text to inform7_cslib.cs) +partial class Story { + protected internal int i7_static_himem; +} += + += +void CSMemoryModel::end(code_generation *gen) { + segmentation_pos saved = CodeGen::select(gen, cs_memory_array_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("};\n"); + OUTDENT; + WRITE("}\n"); + CodeGen::deselect(gen, saved); + + saved = CodeGen::select(gen, cs_constructor_I7CGS); + OUT = CodeGen::current(gen); + WRITE("i7_set_initial_memory();\n"); + WRITE("i7_static_himem = %d;\n", CS_GEN_DATA(memdata.himem)); + CodeGen::deselect(gen, saved); +} + +@ What goes into memory are arrays: memory is allocated only in the form of +such arrays, which are declared one at a time. See //Vanilla Constants//. + + += +int CSMemoryModel::begin_array(code_generator *gtr, code_generation *gen, + text_stream *array_name, inter_symbol *array_s, inter_tree_node *P, int format, + int zero_count, segmentation_pos *saved) { + Str::clear(CS_GEN_DATA(memdata.array_name)); + WRITE_TO(CS_GEN_DATA(memdata.array_name), "%S", array_name); + CS_GEN_DATA(memdata.entry_count) = 0; + if (ConstantInstruction::list_format(P) == CONST_LIST_FORMAT_GRAMMAR) + @ + else + @; +} + +@ Command-grammar arrays are handled differently: note the return value |FALSE|, +which tells Vanilla not to call us again about this array. + +@ = + if (saved) *saved = CodeGen::select(gen, cs_verb_arrays_I7CGS); + VanillaIF::verb_grammar(gtr, gen, array_s, P); + return FALSE; + +@ = + if (saved) *saved = CodeGen::select(gen, cs_arrays_I7CGS); + text_stream *format_name = I"unknown"; + @; + @; + if ((format == TABLE_ARRAY_FORMAT) || (format == BUFFER_ARRAY_FORMAT)) + @; + for (int i=0; i = + switch (format) { + case BYTE_ARRAY_FORMAT: format_name = I"byte"; break; + case WORD_ARRAY_FORMAT: format_name = I"word"; break; + case BUFFER_ARRAY_FORMAT: format_name = I"buffer"; break; + case TABLE_ARRAY_FORMAT: format_name = I"table"; break; + } + +@ Crucially, the array names are |#define| constants declared up near the top +of the source code: they are not variables with pointer types, or something +like that. This means they can legally be used as values elsewhere in memory, +or as initial values of variables, and so on. + +Object, class and function names can also legally appear as array entries, +because they too are defined constants, equal to their IDs: see //C# Object Model//. + +@ = + segmentation_pos saved = CodeGen::select(gen, cs_predeclarations_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("const int "); + CSNamespace::mangle(gtr, OUT, array_name); + WRITE(" = %d; /* = position in memory of %S array %S */\n", + CS_GEN_DATA(memdata.himem), format_name, array_name); + if (array_s) + SymbolAnnotation::set_i(array_s, C_ARRAY_ADDRESS_IANN, + (inter_ti) CS_GEN_DATA(memdata.himem)); + CodeGen::deselect(gen, saved); + +@ Of course, right now we don't know |N|, the extent of the array. So we will +refer to this with a constant like |i7_mgl_myarray__xt| (XT meaning "extent"), +which we will retrospectively predefine when the array ends. + +@ = + TEMPORARY_TEXT(extname) + CSNamespace::mangle(gtr, extname, array_name); + WRITE_TO(extname, "__xt"); + CSMemoryModel::array_entry(gtr, gen, extname, format); + DISCARD_TEXT(extname) + +@ The call to //CSMemoryModel::begin_array// is then followed by a series of calls to: + += +void CSMemoryModel::array_entry(code_generator *gtr, code_generation *gen, + text_stream *entry, int format) { + segmentation_pos saved = CodeGen::select(gen, cs_memory_array_I7CGS); + text_stream *OUT = CodeGen::current(gen); + if ((format == TABLE_ARRAY_FORMAT) || (format == WORD_ARRAY_FORMAT)) + @ + else + @; + CodeGen::deselect(gen, saved); + CS_GEN_DATA(memdata.entry_count)++; +} + +@ = + WRITE(" (byte) %S, /* %d */\n", entry, CS_GEN_DATA(memdata.himem)); + CS_GEN_DATA(memdata.himem) += 1; + +@ Note that |I7BYTE_0| and so on are macros and not functions (see below): they +use only arithmetic operations which can be constant-folded by the C compiler, +and therefore if |X| is a valid constant-context expression in C then so is +|I7BYTE_0(X)|. + +@ = + WRITE(" Inform.Process.I7BYTE_0(%S), Inform.Process.I7BYTE_1(%S), Inform.Process.I7BYTE_2(%S), Inform.Process.I7BYTE_3(%S), /* %d */\n", + entry, entry, entry, entry, CS_GEN_DATA(memdata.himem)); + CS_GEN_DATA(memdata.himem) += 4; + +@ When all the entries have been placed, the following is called. It does nothing +except to predeclare the extent constant. + += +void CSMemoryModel::end_array(code_generator *gtr, code_generation *gen, int format, + int zero_count, segmentation_pos *saved) { + segmentation_pos x_saved = CodeGen::select(gen, cs_predeclarations_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("const int "); + CSNamespace::mangle(gtr, OUT, CS_GEN_DATA(memdata.array_name)); + WRITE("__xt = %d;\n", CS_GEN_DATA(memdata.entry_count)-1); + CodeGen::deselect(gen, x_saved); + if (saved) CodeGen::deselect(gen, *saved); +} + +@ The primitives for byte and word lookup have the signatures: + += (text) +primitive !lookup val val -> val +primitive !lookupbyte val val -> val += + += +int CSMemoryModel::handle_store_by_ref(code_generation *gen, inter_tree_node *ref) { + if (ReferenceInstruction::node_is_ref_to(gen->from, ref, LOOKUP_BIP)) return TRUE; + if (ReferenceInstruction::node_is_ref_to(gen->from, ref, LOOKUPBYTE_BIP)) return TRUE; + return FALSE; +} + +int CSMemoryModel::invoke_primitive(code_generation *gen, inter_ti bip, inter_tree_node *P) { + text_stream *OUT = CodeGen::current(gen); + switch (bip) { + case LOOKUP_BIP: + WRITE("proc.i7_read_word("); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(")"); + break; + case LOOKUPBYTE_BIP: + WRITE("proc.i7_read_byte("); VNODE_1C; WRITE(" + "); VNODE_2C; WRITE(")"); + break; + default: + return NOT_APPLICABLE; + } + return FALSE; +} + +@ So, then, time to write some more of the C library. We are going to need to +define the macros |I7BYTE_0| to |I7BYTE_3|, and also the functions |i7_read_word| +and |i7_read_byte|, used just above. But we start with the function which resets +memory to its initial state when a process begins, and with the stack empty. + +Note that we fill in ten bytes of the 64-byte header block of memory: + +(*) The Release number as a big-endian 16-bit value at |0x34-0x35|; +(*) The Serial code as six ASCII characters (in practice digits) at |0x36-0x3B|. + +We carefully defined those two constants, if they exist, before the inclusion point of +the C library in order that the conditional compilations in |i7_initialise_memory_and_stack| +will work correctly. See //CSNamespace::declare_constant//. + +The rest of the header area remains all zeros. + += (text to inform7_cslib.cs) +partial class Story { + internal byte[] i7_initial_memory; +} + +partial class Process { + internal int i7_static_himem; + void i7_initialise_memory_and_stack() { + i7_static_himem = story.i7_static_himem; + byte[] mem = new byte[i7_static_himem]; + // TODO: we should use a copy method instead. + for (int i=0; i= i7_static_himem)) { + Console.Write("Memory access out of range: "); Console.WriteLine(byte_position); + //TODO use native exception? + i7_fatal_exit(); + } + return (short) (data[byte_position + 1] + + 0x1000*data[byte_position + 0]); + } + + public int i7_read_word(int array_address, int array_index) { + byte[] data = state.memory; + int byte_position = array_address + 4*array_index; + if ((byte_position < 0) || (byte_position >= i7_static_himem)) { + Console.Write("Memory access out of range: "); Console.WriteLine(byte_position); + i7_fatal_exit(); + } + return (int) data[byte_position + 3] + + 0x1000*((int) data[byte_position + 2]) + + 0x10000*((int) data[byte_position + 1]) + + 0x1000000*((int) data[byte_position + 0]); + } += + +@ Writing values is again easy for bytes, but harder for words since they must +be broken up into bytes written in sequence. + +Note that we make use of macros and not functions so that it is possible to +express the fragments of a packed word in constant context: this is essential +for our array initialisations. + +Note that short words do not need to be written. + += (text to inform7_cslib.cs) + public static byte I7BYTE_0(int V) => (byte) ((V & 0xFF000000) >> 24); + public static byte I7BYTE_1(int V) => (byte) ((V & 0x00FF0000) >> 16); + public static byte I7BYTE_2(int V) => (byte) ((V & 0x0000FF00) >> 8); + public static byte I7BYTE_3(int V) => (byte) (V & 0x000000FF); + + void i7_write_byte(int address, byte new_val) { + state.memory[address] = new_val; + } + + void i7_write_word(int address, int array_index,int new_val) { + int byte_position = address + 4*array_index; + if ((byte_position < 0) || (byte_position >= i7_static_himem)) { + Console.Write("Memory access out of range: "); Console.WriteLine(byte_position); + i7_fatal_exit(); + } + state.memory[byte_position] = I7BYTE_0(new_val); + state.memory[byte_position+1] = I7BYTE_1(new_val); + state.memory[byte_position+2] = I7BYTE_2(new_val); + state.memory[byte_position+3] = I7BYTE_3(new_val); + } += + +@ = +void CSMemoryModel::word_to_byte(code_generator *gtr, code_generation *gen, + OUTPUT_STREAM, text_stream *val, int b) { + WRITE("I7BYTE_%d(%S)", b, val); +} + +@ The seven primitive operations on storage need to be implemented for byte +and word lookups by the following pair of functions. Note that if |way| is +|i7_lvalue_SET| then |i7_change_byte| is equivalent to |i7_write_byte| and +|i7_change_word| to |i7_write_word|, except that they return the value as set. + += (text to inform7_cslib.cs) + public byte i7_change_byte(int address, byte new_val, int way) { + byte old_val = i7_read_byte(address); + byte return_val = new_val; + switch (way) { + case i7_lvalue_PREDEC: return_val = (byte)(old_val-1); new_val = (byte)(old_val-1); break; + case i7_lvalue_POSTDEC: return_val = old_val; new_val = (byte)(old_val-1); break; + case i7_lvalue_PREINC: return_val = (byte)(old_val+1); new_val = (byte)(old_val+1); break; + case i7_lvalue_POSTINC: return_val = old_val; new_val = (byte)(old_val+1); break; + case i7_lvalue_SETBIT: new_val = (byte)(old_val | new_val); return_val = new_val; break; + case i7_lvalue_CLEARBIT: new_val = (byte)(old_val &(~new_val)); return_val = new_val; break; + } + i7_write_byte(address, new_val); + return return_val; + } + + public int i7_change_word(int array_address, int array_index, + int new_val, int way) { + byte[] data = state.memory; + int old_val = i7_read_word(array_address, array_index); + int return_val = new_val; + switch (way) { + case i7_lvalue_PREDEC: return_val = old_val-1; new_val = old_val-1; break; + case i7_lvalue_POSTDEC: return_val = old_val; new_val = old_val-1; break; + case i7_lvalue_PREINC: return_val = old_val+1; new_val = old_val+1; break; + case i7_lvalue_POSTINC: return_val = old_val; new_val = old_val+1; break; + case i7_lvalue_SETBIT: new_val = old_val | new_val; return_val = new_val; break; + case i7_lvalue_CLEARBIT: new_val = old_val &(~new_val); return_val = new_val; break; + } + i7_write_word(array_address, array_index, new_val); + return return_val; + } += + +@ The stack is very simple; it can be pushed or pulled, but there's otherwise +no access to it. + += (text to inform7_cslib.cs) + internal void i7_debug_stack(string N) { + #if I7_LOG_STACK_STATE + Console.WriteLine("Called {0}: stack {1} ", N, state.stack_pointer); + for (int i=0; i ", state.stack[i]); + Console.WriteLine(); + #endif + } + + internal int i7_pull() { + if (state.stack_pointer <= 0) { + Console.WriteLine("Stack underflow"); + i7_fatal_exit(); + } + return state.stack[--(state.stack_pointer)]; + } + + internal void i7_push(int x) { + if (state.stack_pointer >= State.I7_ASM_STACK_CAPACITY) { + Console.WriteLine("Stack overflow"); + i7_fatal_exit(); + } + state.stack[state.stack_pointer++] = x; + } += + +@ When processes are running, they take periodic "snapshots" of their states +so that these can if necessary be returned to. (For IF works, this is how the +UNDO command works; snapshots are taken once each turn.) + +Taking a snapshot, or restoring the state from an existing snapshot, inevitably +means making a copy of state data. This has to be a deep copy, because the +|State| structure is really just a collection of pointers to arrays in +memory; copying only the pointers would not be good enough. + +For the same reason, an |State| cannot simply be discarded without causing +a memory leak, so we provide a destructor function. + + += (text to inform7_cslib.cs) + void i7_copy_state(State to, State from) { + //TODO move these to State + to.himem = from.himem; + to.memory = new byte[i7_static_himem]; + //TODO copyarray + for (int i=0; i 0) { + int v = proc.i7_pull(); + if (argc < 5) a[argc++] = v; + varargc--; + } + + int rv = 0; + switch (selector) { + case GlkOpcodes.i7_glk_gestalt: + rv = proc.i7_miniglk_gestalt(a[0]); break; + + /* Characters */ + case GlkOpcodes.i7_glk_char_to_lower: + rv = proc.i7_miniglk_char_to_lower(a[0]); break; + case GlkOpcodes.i7_glk_char_to_upper: + rv = proc.i7_miniglk_char_to_upper(a[0]); break; + case i7_glk_buffer_to_lower_case_uni: + for (int pos=0; pos= 0x41) && (c <= 0x5A)) || + ((c >= 0xC0) && (c <= 0xD6)) || + ((c >= 0xD8) && (c <= 0xDE))) c += 32; + return c; + } + + internal int i7_miniglk_char_to_upper(int c) { + if (((c >= 0x61) && (c <= 0x7A)) || + ((c >= 0xE0) && (c <= 0xF6)) || + ((c >= 0xF8) && (c <= 0xFE))) c -= 32; + return c; + } +} += + +@h Miniglk data. +Each ss needs to keep track of its own files, streams, windows and events, +which are wrapped up in a |MiniGlkData| class as follows: + += (text to inform7_cslib.cs) +class MiniGlkData { + internal const int I7_MINIGLK_MAX_FILES = 128; + internal const int I7_MINIGLK_MAX_STREAMS = 128; + internal const int I7_MINIGLK_MAX_WINDOWS = 128; + internal const int I7_MINIGLK_RING_BUFFER_SIZE = 32; + /* streams */ + internal MgStream[] memory_streams; + int stdout_stream_id, stderr_stream_id; + /* files */ + internal MgFile[] files; + internal int no_files; + /* windows */ + internal MgWindow[] windows; + internal int no_windows; + /* events */ + internal MgEvent[] events_ring_buffer; + internal int rb_back, rb_front; + internal int no_line_events; + + internal MiniGlkData(Process proc) { + memory_streams = new MgStream[MiniGlkData.I7_MINIGLK_MAX_STREAMS]; + for (int i=0; i= MiniGlkData.I7_MINIGLK_MAX_FILES) { + Console.Error.WriteLine("Out of files"); i7_fatal_exit(); + } + int id = miniglk.no_files++; + miniglk.files[id].usage = 0; + miniglk.files[id].name = 0; + miniglk.files[id].rock = 0; + miniglk.files[id].handle = null; + miniglk.files[id].leafname = null; + return id; + } + + long i7_mg_fseek(int id, int pos, int origin) { + if ((id < 0) || (id >= MiniGlkData.I7_MINIGLK_MAX_FILES)) { + Console.Error.WriteLine("Bad file ID"); i7_fatal_exit(); + } + if (miniglk.files[id].handle == null) { + Console.Error.WriteLine("File not open"); i7_fatal_exit(); + } + return miniglk.files[id].handle.Seek(pos, (SeekOrigin)origin); + } + + long i7_mg_ftell(int id) { + if ((id < 0) || (id >= MiniGlkData.I7_MINIGLK_MAX_FILES)) { + Console.Error.WriteLine("Bad file ID"); i7_fatal_exit(); + } + if (miniglk.files[id].handle == null) { + Console.Error.WriteLine("File not open"); i7_fatal_exit(); + } + long t = miniglk.files[id].handle.Position; + return t; + } + + int i7_mg_fopen(int id, int mode) { + if ((id < 0) || (id >= MiniGlkData.I7_MINIGLK_MAX_FILES)) { + Console.Error.WriteLine("Bad file ID"); i7_fatal_exit(); + } + if (miniglk.files[id].handle != null) { + Console.Error.WriteLine("File already open"); i7_fatal_exit(); + } + FileAccess access = FileAccess.Read; + FileMode n_mode = FileMode.Open; + switch (mode) { + case Process.i7_filemode_Write: access = FileAccess.Write; n_mode = FileMode.Create; break; + case Process.i7_filemode_Read: access = FileAccess.Read; n_mode = FileMode.Open; break; + case Process.i7_filemode_ReadWrite: access = FileAccess.ReadWrite; n_mode = FileMode.OpenOrCreate; break; + case Process.i7_filemode_WriteAppend: access = FileAccess.Write; n_mode = FileMode.OpenOrCreate; break; + } + FileStream h = File.Open(miniglk.files[id].leafname, n_mode, access); + if (h == null) return 0; + miniglk.files[id].handle = h; + if (mode == Process.i7_filemode_WriteAppend) i7_mg_fseek( id, 0, (int)SeekOrigin.End); + return 1; + } + + void i7_mg_fclose(int id) { + if ((id < 0) || (id >= MiniGlkData.I7_MINIGLK_MAX_FILES)) { + Console.Error.WriteLine("Bad file ID"); i7_fatal_exit(); + } + if (miniglk.files[id].handle == null) { + Console.Error.WriteLine("File not open"); i7_fatal_exit(); + } + miniglk.files[id].handle.Close(); + miniglk.files[id].handle = null; + } + + + void i7_mg_fputc(int c, int id) { + if ((id < 0) || (id >= MiniGlkData.I7_MINIGLK_MAX_FILES)) { + Console.Error.WriteLine("Bad file ID"); i7_fatal_exit(); + } + if (miniglk.files[id].handle == null) { + Console.Error.WriteLine("File not open"); i7_fatal_exit(); + } + miniglk.files[id].handle.WriteByte((byte)c); + } + + int i7_mg_fgetc(int id) { + if ((id < 0) || (id >= MiniGlkData.I7_MINIGLK_MAX_FILES)) { + Console.Error.WriteLine("Bad file ID"); i7_fatal_exit(); + } + if (miniglk.files[id].handle == null) { + Console.Error.WriteLine("File not open"); i7_fatal_exit(); + } + int c = miniglk.files[id].handle.ReadByte(); + return c; + } += + +@ This allows us to implement |glk_fileref_create_by_name| and |glk_fileref_does_file_exist|. + + += (text to inform7_cslib.cs) + internal int i7_miniglk_fileref_create_by_name(int usage, + int name, int rock) { + int id = i7_mg_new_file(); + miniglk.files[id].usage = usage; + miniglk.files[id].name = name; + miniglk.files[id].rock = rock; + + var L = new System.Text.StringBuilder(); + + for (int i=0; i < 127; i++) { + //FIXME: not unicode safe + char b = (char) i7_read_byte(name+1+i); + if (b == 0) break; + L.Append(b); + } + + L.Append(".glkdata"); + miniglk.files[id].leafname = L.ToString(); + return id; + } + + internal int i7_miniglk_fileref_does_file_exist(int id) { + if ((id < 0) || (id >= MiniGlkData.I7_MINIGLK_MAX_FILES)) { + Console.Error.WriteLine("Bad file ID"); i7_fatal_exit(); + } + if (miniglk.files[id].handle != null) return 1; + if (i7_mg_fopen( id, Process.i7_filemode_Read) != 0) { + i7_mg_fclose( id); return 1; + } + return 0; + } += + +@h Streams. +These are channels for input/output, carrying bytes (which are usually characters). + += (text to inform7_cslib.cs) + internal static MgStream i7_mg_new_stream(Stream F, int win_id) { + MgStream S = new MgStream(); + S.to_file = F; + S.to_file_id = -1; + S.to_memory = null; + S.memory_used = 0; + S.memory_capacity = 0; + S.write_here_on_closure = 0; + S.write_limit = 0; + S.previous_id = 0; + S.active = 0; + S.encode_UTF8 = 0; + S.char_size = 4; + S.chars_read = 0; + S.read_position = 0; + S.end_position = 0; + S.owned_by_window_id = win_id; + S.style = null; + S.fixed_pitch = 0; + S.composite_style = null; + return S; + } + + internal int i7_mg_open_stream(Stream F, int win_id) { + for (int i=0; i= MiniGlkData.I7_MINIGLK_MAX_STREAMS)) { + Console.Error.WriteLine("Stream ID {0} out of range", id); i7_fatal_exit(); + } + + if (miniglk.memory_streams[id].to_file_id >= 0) { + int origin = 0; + switch (seekmode) { + case i7_seekmode_Start: origin = (int)SeekOrigin.Begin; break; + case i7_seekmode_Current: origin = (int)SeekOrigin.Current; break; + case i7_seekmode_End: origin = (int)SeekOrigin.End; break; + default: Console.Error.WriteLine("Unknown seekmode"); i7_fatal_exit(); break; + } + i7_mg_fseek( miniglk.memory_streams[id].to_file_id, pos, origin); + } else { + Console.Error.WriteLine("glk_stream_set_position supported only for file streams"); + i7_fatal_exit(); + } + } + + internal int i7_miniglk_stream_get_position(int id) { + if ((id < 0) || (id >= MiniGlkData.I7_MINIGLK_MAX_STREAMS)) { + Console.Error.WriteLine("Stream ID {0} out of range", id); i7_fatal_exit(); + } + + if (miniglk.memory_streams[id].to_file_id >= 0) { + return (int) i7_mg_ftell( miniglk.memory_streams[id].to_file_id); + } + return (int) miniglk.memory_streams[id].memory_used; + } += + +@ Each ss has a current stream, and |glk_stream_get_current| and +|glk_stream_set_current| give access to this. + + += (text to inform7_cslib.cs) + internal int i7_miniglk_stream_get_current() { + return state.current_output_stream_ID; + } + + internal void i7_miniglk_stream_set_current(int id) { + if ((id < 0) || (id >= MiniGlkData.I7_MINIGLK_MAX_STREAMS)) { + Console.Error.WriteLine("Stream ID {0} out of range", id); i7_fatal_exit(); + } + state.current_output_stream_ID = id; + } += + +@ The thing which is "current" about the current stream is that this is where +characters are written to. The following implements |glk_put_char_stream|. + += (text to inform7_cslib.cs) + internal void i7_mg_put_to_stream(int rock, char c) { + + if (receiver == null) Console.OpenStandardOutput().WriteByte((byte) c); + else receiver(rock, c, miniglk.memory_streams[state.current_output_stream_ID].composite_style); + } + + internal void i7_miniglk_put_char_stream(int stream_id, int x) { + if (miniglk.memory_streams[stream_id].to_file != null) { + int win_id = miniglk.memory_streams[stream_id].owned_by_window_id; + int rock = -1; + if (win_id >= 1) rock = i7_mg_get_window_rock( win_id); + uint c = (uint) x; + if (use_UTF8 != 0) { + if (c >= 0x200000) { /* invalid Unicode */ + i7_mg_put_to_stream(rock, '?'); + } else if (c >= 0x10000) { + i7_mg_put_to_stream(rock, 0xF0 + (c >> 18)); + i7_mg_put_to_stream(rock, 0x80 + ((c >> 12) & 0x3f)); + i7_mg_put_to_stream(rock, 0x80 + ((c >> 6) & 0x3f)); + i7_mg_put_to_stream(rock, 0x80 + (c & 0x3f)); + } + if (c >= 0x800) { + i7_mg_put_to_stream( rock, (char) (0xE0 + (c >> 12))); + i7_mg_put_to_stream( rock, (char) (0x80 + ((c >> 6) & 0x3f))); + i7_mg_put_to_stream( rock, (char) (0x80 + (c & 0x3f))); + } else if (c >= 0x80) { + i7_mg_put_to_stream( rock, (char) (0xC0 + (c >> 6))); + i7_mg_put_to_stream( rock, (char) (0x80 + (c & 0x3f))); + } else i7_mg_put_to_stream( rock, (char) c); + } else { + i7_mg_put_to_stream( rock, (char) c); + } + } else if (miniglk.memory_streams[stream_id].to_file_id >= 0) { + i7_mg_fputc( (int) x, miniglk.memory_streams[stream_id].to_file_id); + miniglk.memory_streams[stream_id].end_position++; + } else { + if (miniglk.memory_streams[stream_id].memory_used >= miniglk.memory_streams[stream_id].memory_capacity) { + long needed = 4*miniglk.memory_streams[stream_id].memory_capacity; + if (needed == 0) needed = 1024; + byte[] new_data = new byte[needed]; + if (new_data == null) { + Console.Error.WriteLine("Out of memory"); i7_fatal_exit(); + } + for (long i=0; i= 0) { + miniglk.memory_streams[stream_id].chars_read++; + return i7_mg_fgetc( miniglk.memory_streams[stream_id].to_file_id); + } + return 0; + } + +@ And finally |glk_stream_close|, which is far from being an empty courtesy: +we may have to close a file on disc, or we may have to copy a memory buffer into +ss memory. + += (text to inform7_cslib.cs) + internal void i7_miniglk_stream_close(int id, int result) { + if ((id < 0) || (id >= MiniGlkData.I7_MINIGLK_MAX_STREAMS)) { + Console.Error.WriteLine("Stream ID {0} out of range", id); i7_fatal_exit(); + } + if (id == 0) { Console.Error.WriteLine("Cannot close stdout"); i7_fatal_exit(); } + if (id == 1) { Console.Error.WriteLine("Cannot close stderr"); i7_fatal_exit(); } + if (miniglk.memory_streams[id].active == 0) { + Console.Error.WriteLine("Stream {0} already closed", id); i7_fatal_exit(); + } + if (state.current_output_stream_ID == id) + state.current_output_stream_ID = miniglk.memory_streams[id].previous_id; + if (miniglk.memory_streams[id].write_here_on_closure != 0) { + if (miniglk.memory_streams[id].char_size == 4) { + for (int i = 0; i < miniglk.memory_streams[id].write_limit; i++) + if (i < miniglk.memory_streams[id].memory_used) + i7_write_word(miniglk.memory_streams[id].write_here_on_closure, i, miniglk.memory_streams[id].to_memory[i]); + else + i7_write_word(miniglk.memory_streams[id].write_here_on_closure, i, 0); + } else { + for (int i = 0; i < miniglk.memory_streams[id].write_limit; i++) + if (i < miniglk.memory_streams[id].memory_used) + i7_write_byte(miniglk.memory_streams[id].write_here_on_closure + i, miniglk.memory_streams[id].to_memory[i]); + else + i7_write_byte(miniglk.memory_streams[id].write_here_on_closure + i, 0); + } + } + if (result == -1) { + i7_push(miniglk.memory_streams[id].chars_read); + i7_push(miniglk.memory_streams[id].memory_used); + } else if (result != 0) { + i7_write_word(result, 0, miniglk.memory_streams[id].chars_read); + i7_write_word(result, 1, miniglk.memory_streams[id].memory_used); + } + if (miniglk.memory_streams[id].to_file_id >= 0) i7_mg_fclose( miniglk.memory_streams[id].to_file_id); + miniglk.memory_streams[id].active = 0; + miniglk.memory_streams[id].memory_used = 0; + } + +@h Windows. +Even a proper Glk implementation isn't presenting any kind of window manager in +the style of Xerox or early Macs: these windows are (borderless, invisible) +rectangles on a plain text grid. + +And in this miniglk, a window is really just a receptacle for a stream of text. +We make no attempt to model how multiple windows might sit, because we're +assuming that either (i) we are being used for a command-line console app which +doesn't treat the Terminal window as two-dimensional, or (ii) we are being linked +into some bigger GUI app which is going to handle everything visual in its own +way anyway. + +Note that we shamelessly claim that all windows are 80 x 8 characters. + += (text to inform7_cslib.cs) + internal int i7_miniglk_window_open(int split, int method, + int size, int wintype, int rock) { + if (miniglk.no_windows >= 128) { + Console.Error.WriteLine("Out of windows"); i7_fatal_exit(); + } + int id = miniglk.no_windows++; + miniglk.windows[id].type = wintype; + miniglk.windows[id].stream_id = i7_mg_open_stream( Console.OpenStandardOutput(), id); + miniglk.windows[id].rock = rock; + return id; + } + + internal int i7_miniglk_set_window(int id) { + if ((id < 0) || (id >= miniglk.no_windows)) { + Console.Error.WriteLine("Window ID {0} out of range", id); i7_fatal_exit(); + } + i7_miniglk_stream_set_current( miniglk.windows[id].stream_id); + return 0; + } + + internal int i7_mg_get_window_rock(int id) { + if ((id < 0) || (id >= miniglk.no_windows)) { + Console.Error.WriteLine("Window ID {0} out of range", id); i7_fatal_exit(); + } + return miniglk.windows[id].rock; + } + + internal int i7_miniglk_window_get_size(int id, int a1, + int a2) { + if (a1 != 0) i7_write_word(a1, 0, 80); + if (a2 != 0) i7_write_word(a2, 0, 8); + return 0; + } += + +@h Events. +Pending events are stored in a ring buffer, where the valid pending events are +those between the |rb_back| and |rb_front| markers, modulo |I7_MINIGLK_RING_BUFFER_SIZE|. +(In practice, this is more than we need for the very simple use that the standard +I7 kits make of events. Still, it does no harm.) + += (text to inform7_cslib.cs) + void i7_mg_add_event_to_buffer(MgEvent e) { + miniglk.events_ring_buffer[miniglk.rb_front] = e; + miniglk.rb_front++; + if (miniglk.rb_front == MiniGlkData.I7_MINIGLK_RING_BUFFER_SIZE) + miniglk.rb_front = 0; + } + + MgEvent i7_mg_get_event_from_buffer() { + if (miniglk.rb_front == miniglk.rb_back) return null; + MgEvent e = miniglk.events_ring_buffer[miniglk.rb_back]; + miniglk.rb_back++; + if (miniglk.rb_back == MiniGlkData.I7_MINIGLK_RING_BUFFER_SIZE) + miniglk.rb_back = 0; + return e; + } += + +@ That enables |glk_select|, an operation by which the caller can choose to +pull an event from the buffer and (optionally) copy its data ihto ss +memory. + += (text to inform7_cslib.cs) + internal int i7_miniglk_select(int/* TODO bool*/ structure) { + MgEvent e = i7_mg_get_event_from_buffer(); + if (e == null) { + Console.Error.WriteLine("No events available to select"); i7_fatal_exit(); + } + if (structure == -1) { + i7_push(e.type); + i7_push(e.win_id); + i7_push(e.val1); + i7_push(e.val2); + } else { + if (structure != 0) { + i7_write_word(structure, 0, e.type); + i7_write_word(structure, 1, e.win_id); + i7_write_word(structure, 2, e.val1); + i7_write_word(structure, 3, e.val2); + } + } + return 0; + } + +@ And also |glk_request_line_event|. This asks the ss's sender function to +compose a command (terminated by 0 or a newline), then makes that it into a +line event and pushes it to the event buffer. The caller can then use |glk_select| +to find out what the command was. + += (text to inform7_cslib.cs) + internal int i7_miniglk_request_line_event(int window_id, + int buffer, int max_len, int init_len) { + MgEvent e = new MgEvent(); + e.type = Process.i7_evtype_LineInput; + e.win_id = window_id; + e.val1 = 1; + e.val2 = 0; + char c; int pos = init_len; + if (sender == null) i7_benign_exit(); + string s = sender(send_count++); + int i = 0; + while (true) { + c = s[i++]; + if ((c == -1) || (c == 0) || (c == '\n') || (c == '\r')) break; + if (pos < max_len) i7_write_byte(buffer + pos++, (byte) c); + } + if (pos < max_len) i7_write_byte(buffer + pos, 0); + else i7_write_byte(buffer + max_len-1, 0); + e.val1 = pos; + i7_mg_add_event_to_buffer(e); + if (miniglk.no_line_events++ == 1000) { + Console.WriteLine("[Too many line events: terminating to prevent hang]"); + i7_benign_exit(); + } + return 0; + } + + internal int i7_miniglk_request_line_event_uni(int window_id, + int buffer, int max_len, int init_len) { + MgEvent e = new MgEvent(); + e.type = Process.i7_evtype_LineInput; + e.win_id = window_id; + e.val1 = 1; + e.val2 = 0; + char c; int pos = init_len; + if (sender == null) i7_benign_exit(); + string s = sender(send_count++); + int i = 0; + while (1) { + c = s[i++]; + if ((c == EOF) || (c == 0) || (c == '\n') || (c == '\r')) break; + if (pos < max_len) i7_write_word(buffer, pos++, c); + } + if (pos < max_len) i7_write_word(buffer, pos, 0); + else i7_write_word(proc, buffer, max_len-1, 0); + e.val1 = pos; + i7_mg_add_event_to_buffer(e); + if (pminiglk.no_line_events++ == 1000) { + Console.WriteLine("[Too many line events: terminating to prevent hang]\n"); + i7_benign_exit(); + } + return 0; + } +} + +@h Styling. +This happens outside of miniglk. Glk does have a concept of text styles, but one +which is difficult to marry to CSS-esque styles in the way we might want here. +So we provide this additional styling functionality outside of the Glk specification. +When |which| is 1, we're essentially emulating Inform 6's |font| statement; when +it is 2, we're emulation |style|, though an enhanced version capable of more than +the three built-in styles |bold|, |italic| and |reverse|. + += (text to inform7_cslib.cs) +partial class Defaults { + public static void i7_default_stylist(Process proc, int which, int what) { + if (which == 1) { + proc.miniglk.memory_streams[proc.state.current_output_stream_ID].fixed_pitch = what; + } else { + proc.miniglk.memory_streams[proc.state.current_output_stream_ID].style = null; + switch (what) { + case 0: break; + case 1: proc.miniglk.memory_streams[proc.state.current_output_stream_ID].style = "bold"; break; + case 2: proc.miniglk.memory_streams[proc.state.current_output_stream_ID].style = "italic"; break; + case 3: proc.miniglk.memory_streams[proc.state.current_output_stream_ID].style = "reverse"; break; + default: { + #if i7_mgl_BASICINFORMKIT + int L = + i7_fn_TEXT_TY_CharacterLength( what, 0, 0, 0, 0, 0, 0); + if (L > 127) L = 127; + for (int i=0; ifrom, CSNamespace::sweep_for_locals, gen, NULL, LOCAL_IST); +} + +void CSNamespace::sweep_for_locals(inter_tree *I, inter_tree_node *P, void *state) { + inter_symbol *var_name = LocalInstruction::variable(P); + TEMPORARY_TEXT(T) + WRITE_TO(T, "local_%S", InterSymbol::identifier(var_name)); + InterSymbol::set_translate(var_name, T); + DISCARD_TEXT(T) +} + +@ Constants in Inter are indeed directly converted to |#define|d constants in C, +but with their names of course mangled. + +For the reason why |Serial| and |Release| are placed higher-up in the file, see +//C# Memory Model//. + += +void CSNamespace::declare_constant(code_generator *gtr, code_generation *gen, + inter_symbol *const_s, int form, text_stream *val) { + text_stream *name = InterSymbol::trans(const_s); + int seg = cs_constants_I7CGS; + if (Str::eq(name, I"Serial")) seg = cs_ids_and_maxima_I7CGS; + if (Str::eq(name, I"Release")) seg = cs_ids_and_maxima_I7CGS; + if (Str::eq(name, I"BASICINFORMKIT")) seg = cs_ids_and_maxima_I7CGS; + if (Str::eq(name, I"DICT_WORD_SIZE")) seg = cs_ids_and_maxima_I7CGS; + segmentation_pos saved = CodeGen::select_layered(gen, seg, + ConstantInstruction::constant_depth(const_s)); + text_stream *OUT = CodeGen::current(gen); + WRITE("const int "); + CSNamespace::mangle(gtr, OUT, name); + /* WRITE(";\n"); + CodeGen::deselect(gen, saved); + saved = CodeGen::select(gen, cs_constructor_I7CGS); + OUT = CodeGen::current(gen); + CSNamespace::mangle(gtr, OUT, name); */ + WRITE(" = "); + VanillaConstants::definition_value(gen, form, const_s, val); + WRITE(";\n"); + CodeGen::deselect(gen, saved); +} diff --git a/inter/final-module/Chapter 6/C# Object Model.w b/inter/final-module/Chapter 6/C# Object Model.w new file mode 100644 index 0000000000..88f9b512dc --- /dev/null +++ b/inter/final-module/Chapter 6/C# Object Model.w @@ -0,0 +1,1114 @@ +[CSObjectModel::] C# Object Model. + +How objects, classes and properties are compiled to C#. + +@h Introduction. + += +void CSObjectModel::initialise(code_generator *gtr) { + METHOD_ADD(gtr, PSEUDO_OBJECT_MTID, CSObjectModel::pseudo_object); + METHOD_ADD(gtr, DECLARE_INSTANCE_MTID, CSObjectModel::declare_instance); + METHOD_ADD(gtr, DECLARE_KIND_MTID, CSObjectModel::declare_kind); + + METHOD_ADD(gtr, DECLARE_PROPERTY_MTID, CSObjectModel::declare_property); + METHOD_ADD(gtr, ASSIGN_PROPERTY_MTID, CSObjectModel::assign_property); + METHOD_ADD(gtr, ASSIGN_PROPERTIES_MTID, CSObjectModel::assign_properties); +} + +@ + +@d MAX_CS_OBJECT_TREE_DEPTH 256 + += +typedef struct CS_generation_object_model_data { + int owner_id_count; + struct CS_property_owner *arrow_chain[MAX_CS_OBJECT_TREE_DEPTH]; + int property_id_counter; + struct CS_property_owner *current_owner; + struct dictionary *declared_properties; + struct linked_list *declared_owners; /* of |CS_property_owner| */ + struct CS_property_owner *compass_instance; + struct CS_property_owner *direction_kind; + int value_ranges_needed; + int value_property_holders_needed; + int Class_either_or_properties_not_set; +} CS_generation_object_model_data; + +void CSObjectModel::initialise_data(code_generation *gen) { + CS_GEN_DATA(objdata.owner_id_count) = 1; + CS_GEN_DATA(objdata.property_id_counter) = 0; + CS_GEN_DATA(objdata.declared_properties) = Dictionaries::new(1024, FALSE); + CS_GEN_DATA(objdata.declared_owners) = NEW_LINKED_LIST(CS_property_owner); + for (int i=0; i<128; i++) CS_GEN_DATA(objdata.arrow_chain)[i] = NULL; + CS_GEN_DATA(objdata.compass_instance) = NULL; + CS_GEN_DATA(objdata.value_ranges_needed) = FALSE; + CS_GEN_DATA(objdata.value_property_holders_needed) = FALSE; + CS_GEN_DATA(objdata.Class_either_or_properties_not_set) = TRUE; +} + +void CSObjectModel::begin(code_generation *gen) { + CSObjectModel::initialise_data(gen); + CSObjectModel::declare_metaclasses(gen); +} + += + += (text to inform7_cslib.cs) +partial class Story { + protected internal int i7_max_objects; + protected internal int i7_no_property_ids; += + += +void CSObjectModel::end(code_generation *gen) { + CSObjectModel::write_i7_initialiser(gen); + CSObjectModel::write_i7_initialise_object_tree(gen); + CSObjectModel::define_object_value_regions(gen); + CSObjectModel::compile_ofclass_array(gen); + CSObjectModel::compile_gprop_functions(gen); + segmentation_pos saved = CodeGen::select(gen, cs_constructor_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("i7_max_objects = I7VAL_STRINGS_BASE;\n"); + WRITE("i7_no_property_ids = %d;\n", CS_GEN_DATA(objdata.property_id_counter)); + CodeGen::deselect(gen, saved); + CSObjectModel::make_enumerated_property_arrays(gen); +} + +@h The object value-space. +Inter requires that the following values must be distinguishable at runtime: + +(a) Instances of object; +(b) Classes, which include kinds of object such as "container", but not other +kinds such as "number"; +(c) Constant text values -- note: this does not mean values of the I7 "text" +kind, this means only text literals in Inter; +(d) Functions; +(e) 0, which is also the value of the non-object |nothing|. + +Note that there is no requirement for these ranges of value to be contiguous, +or to exhaust the whole range of 32-bit values (and they do not). We provide +a function |i7_metaclass| which returns |Class|, |Object|, |String|, |Routine| +or 0 in cases (a) to (e), or 0 for any values not fitting any of these: this +function implements the |!metaclass| primitive. + +@ In this C# runtime, |nothing| will be 0, as is mandatory; |Class|, |Object|, +|String| and |Routine| will be 1 to 4 respectively; values from 5 upwards will +be assigned to objects and classes as they arise -- note that these mix freely; +string values will occupy a contiguous range |I7VAL_STRINGS_BASE| to +|I7VAL_FUNCTIONS_BASE-1|; and function values will be in tha range +|I7VAL_FUNCTIONS_BASE| to |0x7FFFFFFF|, though they will certainly not fill it. + += (text to inform7_cslib.cs) + protected internal int i7_functions_base; + protected internal int[] i7_metaclass_of; + protected internal int[] i7_class_of; += + += +void CSObjectModel::define_object_value_regions(code_generation *gen) { + segmentation_pos saved = CodeGen::select(gen, cs_ids_and_maxima_I7CGS); + text_stream *OUT = CodeGen::current(gen); + int b = CS_GEN_DATA(objdata.owner_id_count); + WRITE("const int I7VAL_STRINGS_BASE = %d;\n", b); + WRITE("const int I7VAL_FUNCTIONS_BASE = %d;\n", b + CSLiteralsModel::size_of_String_area(gen)); + CodeGen::deselect(gen, saved); + saved = CodeGen::select(gen, cs_constructor_I7CGS); + OUT = CodeGen::current(gen); + WRITE("i7_metaclass_of = new[] {\n"); INDENT; + WRITE("0\n"); + CS_property_owner *co; + LOOP_OVER_LINKED_LIST(co, CS_property_owner, CS_GEN_DATA(objdata.declared_owners)) { + WRITE(", "); + if (co->is_class) Generators::mangle(gen, OUT, I"Class"); + else Generators::mangle(gen, OUT, I"Object"); + WRITE("\n"); + } + OUTDENT; WRITE(" };\n"); + WRITE("i7_strings_base = I7VAL_STRINGS_BASE;\n"); + WRITE("i7_functions_base = I7VAL_FUNCTIONS_BASE;\n", b + CSLiteralsModel::size_of_String_area(gen)); + CodeGen::deselect(gen, saved); +} + +@ Those decisions give us the following |i7_metaclass| function: + += (text to inform7_cslib.cs) + protected internal readonly int i7_special_class_Routine; + protected internal readonly int i7_special_class_String; + protected internal readonly int i7_special_class_Class; + protected internal readonly int i7_special_class_Object; + protected internal int i7_metaclass(int id) { + if (id <= 0) return 0; + if (id >= i7_functions_base) return i7_special_class_Routine; + if (id >= i7_strings_base) return i7_special_class_String; + return i7_metaclass_of[id]; + } +} += + +@h Property owners. +We use the term "property owner" to mean either a kind of object, or an instance +of object. This is a little loose since instances of enumerated non-object kinds +can also have properties, but those, as we'll later see, are stored quite differently. + +Each property owner has a unique ID number. The special ID 0 is reserved for |nothing|, +meaning the absence of such an owner, so we can only use 1 upwards; and as we've seen, +1 to 4 are used for the four metaclasses |Class|, |Object|, |String| and |Routine|. +After that, it's first come, first served. + +Each owner has a "class", which is always the name of another owner: except that +the owner |Class| has the class name |Class|, i.e., itself. + +Instances, though not of course classes, will also end up as part of an object +containment tree; so we record the initial state of that three here. For classes, +of course, |initial_parent|, |initial_sibling| and |initial_child| will remain |NULL|. + += +typedef struct CS_property_owner { + int id; + int is_class; + struct text_stream *name; + struct text_stream *class; + struct linked_list *property_values; /* of |CS_pv_pair| */ + struct CS_property_owner *initial_parent; + struct CS_property_owner *initial_sibling; + struct CS_property_owner *initial_child; + CLASS_DEFINITION +} CS_property_owner; + +CS_property_owner *CSObjectModel::new_owner(code_generation *gen, int id, text_stream *name, + text_stream *class_name, int is_class) { + if (Str::len(name) == 0) internal_error("nameless property owner"); + CS_property_owner *co = CREATE(CS_property_owner); + co->id = id; + co->name = Str::duplicate(name); + co->class = Str::duplicate(class_name); + co->is_class = is_class; + co->property_values = NEW_LINKED_LIST(CS_pv_pair); + co->initial_parent = NULL; + co->initial_sibling = NULL; + co->initial_child = NULL; + CS_GEN_DATA(objdata.current_owner) = co; + ADD_TO_LINKED_LIST(co, CS_property_owner, CS_GEN_DATA(objdata.declared_owners)); + return co; +} + +@ The (constant) array |i7_class_of[id]| accepts any ID for a class or instance, +and evaluates to the ID of its classname. So, for example, |i7_class_of[1] == 1| +expresses that the classname of |Class| is |Class| itself. Here we compile +a declaration for that array. + += +void CSObjectModel::compile_ofclass_array(code_generation *gen) { + segmentation_pos saved = CodeGen::select(gen, cs_constructor_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("i7_class_of = new[] { 0"); + CS_property_owner *co; + LOOP_OVER_LINKED_LIST(co, CS_property_owner, CS_GEN_DATA(objdata.declared_owners)) { + WRITE(", "); Generators::mangle(gen, OUT, co->class); + } + WRITE(" };\n"); + CodeGen::deselect(gen, saved); +} + +@ The existence of the |i7_class_of| array at runtime makes it possible to +implement the primitive |!ofclass| reasonably efficiently. Note that it may need +to recurse up the class hierarchy. If A is of class B whose superclass is C, then +|i7_ofclass(A, B)| and |i7_ofclass(A, C)| are both true, as it |i7_ofclass(B, C)|. + += (text to inform7_cslib.cs) +partial class Process { + internal int i7_ofclass(int id, int cl_id) { + if ((id <= 0) || (cl_id <= 0)) return 0; + if (id >= story.i7_functions_base) { + if (cl_id == story.i7_special_class_Routine) return 1; + return 0; + } + if (id >= story.i7_strings_base) { + if (cl_id == story.i7_special_class_String) return 1; + return 0; + } + if (id == story.i7_special_class_Class) { + if (cl_id == story.i7_special_class_Class) return 1; + return 0; + } + if (cl_id == story.i7_special_class_Object) { + if (story.i7_metaclass_of[id] == story.i7_special_class_Object) return 1; + return 0; + } + int cl_found = story.i7_class_of[id]; + while (cl_found != story.i7_special_class_Class) { + if (cl_id == cl_found) return 1; + cl_found = story.i7_class_of[cl_found]; + } + return 0; + } += + +@ Here we compile code to initialise the tree. This happens in two stages: first +the tree is blanked out so that nothing contains anything else, and that's done +with an unchanging function in the C library: + += (text to inform7_cslib.cs) + int i7_max_objects; + int i7_no_property_ids; + void i7_empty_object_tree() { + //TODO: move to State? + i7_max_objects = story.i7_max_objects; + i7_no_property_ids = story.i7_no_property_ids; + state.object_tree_parent = new int[i7_max_objects]; + state.object_tree_child = new int[i7_max_objects]; + state.object_tree_sibling = new int[i7_max_objects]; + for (int i=0; iinitial_parent) { + WRITE("proc.state.object_tree_parent["); + Generators::mangle(gen, OUT, co->name); + WRITE("] = "); + Generators::mangle(gen, OUT, co->initial_parent->name); + WRITE(";\n"); + } + if (co->initial_sibling) { + WRITE("proc.state.object_tree_sibling["); + Generators::mangle(gen, OUT, co->name); + WRITE("] = "); + Generators::mangle(gen, OUT, co->initial_sibling->name); + WRITE(";\n"); + } + if (co->initial_child) { + WRITE("proc.state.object_tree_child["); + Generators::mangle(gen, OUT, co->name); + WRITE("] = "); + Generators::mangle(gen, OUT, co->initial_child->name); + WRITE(";\n"); + } + } + OUTDENT; WRITE("}\n"); + CodeGen::deselect(gen, saved); +} + +@h Runtime classes. +Classes arise either (i) as one of the four fundamental metaclasses, which +we automatically declare at the start of each run, or (ii) when a kind of +object is declared. Here is (i): + += +void CSObjectModel::declare_metaclasses(code_generation *gen) { + CSObjectModel::new_runtime_class(gen, I"Class", NULL, I"Class"); + CSObjectModel::new_runtime_class(gen, I"Object", NULL, I"Class"); + CSObjectModel::new_runtime_class(gen, I"String", NULL, I"Class"); + CSObjectModel::new_runtime_class(gen, I"Routine", NULL, I"Class"); +} + +@ And here is (ii): + += +void CSObjectModel::declare_kind(code_generator *gtr, code_generation *gen, + inter_symbol *kind_s, segmentation_pos *saved) { + if ((kind_s == RunningPipelines::get_symbol(gen->from_step, object_kind_RPSYM)) || + (VanillaObjects::is_kind_of_object(gen, kind_s))) + @ + else if (VanillaObjects::value_kind_with_properties(gen, kind_s)) + CSObjectModel::vph_object(gen, kind_s); +} + +@ = + text_stream *class_name = InterSymbol::trans(kind_s); + text_stream *printed_name = Metadata::optional_textual( + InterPackage::container(kind_s->definition), I"^printed_name"); + text_stream *super_class = NULL; + inter_symbol *super_name = TypenameInstruction::super(kind_s); + if (super_name) super_class = InterSymbol::trans(super_name); + if (Str::len(super_class) == 0) super_class = I"Class"; + CSObjectModel::new_runtime_class(gen, class_name, printed_name, super_class); + +@ In either case (i) or (ii) the following is called: + += +void CSObjectModel::new_runtime_class(code_generation *gen, text_stream *class_name, + text_stream *printed_name, text_stream *super_class) { + int id = CS_GEN_DATA(objdata.owner_id_count)++; + /* int special_class = Str::eq(class_name, I"Class") || + Str::eq(class_name, I"Object") || + Str::eq(class_name, I"String") || + Str::eq(class_name, I"Routine"); */ + segmentation_pos saved = CodeGen::select(gen, /* special_class ? cs_constructor_I7CGS : */ cs_ids_and_maxima_I7CGS); + text_stream *OUT = CodeGen::current(gen); + /* if (!special_class) */ WRITE("/*nrc1*/const int "); + Generators::mangle(gen, OUT, class_name); WRITE(" = %d;\n", id); + CodeGen::deselect(gen, saved); + if (printed_name) { + segmentation_pos saved = CodeGen::select(gen, cs_kinds_symbols_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("/*nrc2*/const int %S = %d;\n", CSTarget::symbols_header_identifier(gen, I"K", printed_name), id); + CodeGen::deselect(gen, saved); + } + CSObjectModel::new_owner(gen, id, class_name, super_class, TRUE); +} + +@h Runtime instances. +These arise either (i) as pseudo-objects provided by kits -- the Inform 7 +compiler never itself generates pseudo-objects; or (ii) as property-holder +objects to hold the properties of an enumerated non-object kind, where one +such object exists for each such kind; or (iii), the most obvious way, as +the runtime form of instances of Inform 7 objects. For example, the rooms, +things and people of a work of interactive fiction would each be cases of (iii). + +Here is (i). After a typical IF run through Inform 7, this produces only +two pseudo-objects, |Compass| and |thedark|. + += +void CSObjectModel::pseudo_object(code_generator *gtr, code_generation *gen, text_stream *obj_name) { + CS_property_owner *obj = CSObjectModel::new_runtime_object(gtr, gen, I"Object", obj_name, -1, FALSE); + if (Str::eq(obj_name, I"Compass")) CS_GEN_DATA(objdata).compass_instance = obj; +} + +@ Here is (ii). Each enumerated kind produces one of these. In a typical +IF run, for example, there is one for the kind "scene". + += +void CSObjectModel::vph_object(code_generation *gen, inter_symbol *kind_s) { + TEMPORARY_TEXT(instance_name) + CSObjectModel::write_vph_identifier(gen, instance_name, kind_s); + CSObjectModel::new_runtime_object(NULL, gen, I"Object", instance_name, -1, FALSE); + DISCARD_TEXT(instance_name) +} + +@ And here is (iii). + += +void CSObjectModel::declare_instance(code_generator *gtr, code_generation *gen, + inter_symbol *inst_s, inter_symbol *kind_s, int enumeration, segmentation_pos *ignored_saved) { + text_stream *printed_name = Metadata::optional_textual( + InterPackage::container(inst_s->definition), I"^printed_name"); + int is_enumerative = FALSE; + if ((kind_s == RunningPipelines::get_symbol(gen->from_step, object_kind_RPSYM)) || + (VanillaObjects::is_kind_of_object(gen, kind_s))) { + @ + } else { + is_enumerative = TRUE; + CSObjectModel::define_constant_for_enumeration(gen, kind_s, inst_s, enumeration); + } + int seg = (is_enumerative)?cs_enum_symbols_I7CGS:cs_instances_symbols_I7CGS; + segmentation_pos saved = CodeGen::select(gen, seg); + text_stream *OUT = CodeGen::current(gen); + WRITE("/*di*/const int %S = %d;\n", + CSTarget::symbols_header_identifier(gen, I"I", printed_name), enumeration); + CodeGen::deselect(gen, saved); +} + +@ = + int c = VanillaObjects::spatial_depth(inst_s); + int is_dir = TypenameInstruction::is_a(kind_s, + RunningPipelines::get_symbol(gen->from_step, direction_kind_RPSYM)); + CS_property_owner *owner = CSObjectModel::new_runtime_object(gtr, gen, + InterSymbol::trans(kind_s), InterSymbol::trans(inst_s), c, is_dir); + enumeration = owner->id; + +@ Whether it's from case (i), (ii) or (iii), we always end up here. Note that +|acount| is negative only in cases (i) and (ii): if it is at least 0, then it +is the "arrow count", that is, its depth in the containment tree. (Calls are +made here in a hierarchical depth-first traverse of the containment tree.) + +All direction objects have to be placed in the |Compass| pseudo-object. + += +CS_property_owner *CSObjectModel::new_runtime_object(code_generator *gtr, code_generation *gen, + text_stream *class_name, text_stream *instance_name, int acount, int is_dir) { + int id = CS_GEN_DATA(objdata.owner_id_count)++; + segmentation_pos saved = CodeGen::select(gen, cs_ids_and_maxima_I7CGS); + text_stream *OUT = CodeGen::current(gen); + if (Str::len(instance_name) == 0) internal_error("nameless instance"); + WRITE("/*nro*/const int "); Generators::mangle(gen, OUT, instance_name); WRITE(" = %d;\n", id); + CodeGen::deselect(gen, saved); + CS_property_owner *this = CSObjectModel::new_owner(gen, id, instance_name, class_name, FALSE); + if (acount >= 0) @; + return this; +} + +@ = + if (acount >= MAX_CS_OBJECT_TREE_DEPTH) internal_error("arrows too deep"); + CS_property_owner *par = NULL; + this->initial_parent = NULL; + if (acount > 0) { + par = CS_GEN_DATA(objdata.arrow_chain)[acount-1]; + if (par == NULL) internal_error("arrows misaligned"); + } else if (is_dir) { + par = CS_GEN_DATA(objdata.compass_instance); + } + if (par) { + if (par->initial_child == NULL) { + par->initial_child = this; + } else { + CS_property_owner *older = par->initial_child; + while ((older) && (older->initial_sibling)) older = older->initial_sibling; + older->initial_sibling = this; + } + this->initial_parent = par; + } + CS_GEN_DATA(objdata.arrow_chain)[acount] = this; + for (int i=acount+1; iname = Str::duplicate(name); + cp->either_or = either_or; + cp->id = CS_GEN_DATA(objdata.property_id_counter)++; + Dictionaries::create(D, name); + Dictionaries::write_value(D, name, (void *) cp); + } else { + cp = Dictionaries::read_value(D, name); + } + return cp; +} + +@ = +CS_property *CSObjectModel::existing_property_by_name(code_generation *gen, + text_stream *name) { + dictionary *D = CS_GEN_DATA(objdata.declared_properties); + if (Dictionaries::find(D, name) == NULL) internal_error("no such property"); + return Dictionaries::read_value(D, name); +} + +@h Declaring properties. + += +void CSObjectModel::declare_property(code_generator *gtr, code_generation *gen, + inter_symbol *prop_s, linked_list *all_forms) { + text_stream *name = InterSymbol::trans(prop_s); + int either_or = VanillaObjects::is_either_or_property(prop_s); + CS_property *cp = CSObjectModel::property_by_name(gen, name, either_or); + text_stream *inner_name = VanillaObjects::inner_property_name(gen, prop_s); + + @; + @; + @; +} + +@ = + segmentation_pos saved = CodeGen::select(gen, cs_predeclarations_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("/*dinc*/const int "); + Generators::mangle(gen, OUT, inner_name); + WRITE(" = %d;\n", cp->id); + CodeGen::deselect(gen, saved); + +@ The Vanilla algorithm says we must make two array entries here, at the start +of the property's runtime metadata array. The Inform 6 generator uses the first +of those entries to say how values of this property are stored at runtime; for +C, though, all properties have the same runtime format, and we don't use the +first entry at all. We'll simply zero it. + +But the second entry is the inner property, as with Inform 6. + +@ = + TEMPORARY_TEXT(val) + WRITE_TO(val, "0"); + Generators::array_entry(gen, val, WORD_ARRAY_FORMAT); + Str::clear(val); + Generators::mangle(gen, val, inner_name); + Generators::array_entry(gen, val, WORD_ARRAY_FORMAT); + DISCARD_TEXT(val) + +@ = + text_stream *pname = Metadata::optional_textual( + InterPackage::container(prop_s->definition), I"^name"); + if (Str::len(pname) > 0) { + int A = SymbolAnnotation::get_i(prop_s, C_ARRAY_ADDRESS_IANN); + if (A > 0) { + segmentation_pos saved = CodeGen::select(gen, cs_property_symbols_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("/*dpnshf*/const int %S = %d;\n", + CSTarget::symbols_header_identifier(gen, I"P", pname), A); + CodeGen::deselect(gen, saved); + } + } + +@h Assigning properties. +Vabilla calls this to assign a property to a single owner: + += +void CSObjectModel::assign_property(code_generator *gtr, code_generation *gen, + inter_symbol *prop_s, inter_pair pair, inter_tree_node *X) { + + int inline_this = FALSE; + if (InterValuePairs::is_symbolic(pair)) { + inter_symbol *S = InterValuePairs::to_symbol_at(pair, X); + if (ConstantInstruction::is_inline(S)) inline_this = TRUE; + } + TEMPORARY_TEXT(val) + CodeGen::select_temporary(gen, val); + CodeGen::pair(gen, X, pair); + CodeGen::deselect_temporary(gen); + CS_property_owner *owner = CS_GEN_DATA(objdata.current_owner); + CS_property *prop = CSObjectModel::existing_property_by_name(gen, + InterSymbol::trans(prop_s)); + CSObjectModel::assign_one_prop(gen, owner, prop, val, inline_this); + DISCARD_TEXT(val) +} + +@ And it calls this to give an array of the property's values for all of the +instances of a single enumerated kind: + += +void CSObjectModel::assign_properties(code_generator *gtr, code_generation *gen, + inter_symbol *kind_s, inter_symbol *prop_s, text_stream *array) { + TEMPORARY_TEXT(mgl) + Generators::mangle(gen, mgl, array); + CS_property_owner *owner = CS_GEN_DATA(objdata.current_owner); + CS_property *prop = CSObjectModel::existing_property_by_name(gen, + InterSymbol::trans(prop_s)); + CSObjectModel::assign_one_prop(gen, owner, prop, mgl, FALSE); + DISCARD_TEXT(mgl) +} + +@ In either case, the following assigns a property value to an owner, though +all it really does is to stash it away for now: + += +typedef struct CS_pv_pair { + struct CS_property *prop; + struct text_stream *val; + int inlined; + CLASS_DEFINITION +} CS_pv_pair; + +void CSObjectModel::assign_one_prop(code_generation *gen, CS_property_owner *owner, + CS_property *prop, text_stream *val, int inline_this) { + CS_pv_pair *pair = CREATE(CS_pv_pair); + pair->prop = prop; + pair->val = Str::duplicate(val); + pair->inlined = inline_this; + ADD_TO_LINKED_LIST(pair, CS_pv_pair, owner->property_values); +} + +@ Creating all those //CS_pv_pair//s was just playing for time, though: eventually +we have to do this -- + + += (text to inform7_cslib.cs) +partial class Story { + public abstract void i7_initialiser(Process proc); +} += + += +void CSObjectModel::write_i7_initialiser(code_generation *gen) { + segmentation_pos saved = CodeGen::select(gen, cs_initialiser_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("override public void i7_initialiser(Inform.Process proc) {\n"); + INDENT; + WRITE("for (int id=0; idname); + WRITE("].address[proc.i7_read_word("); + Generators::mangle(gen, OUT, pair->prop->name); + WRITE(", 1)] = "); + if (pair->inlined) { + WRITE("%S;\n", pair->val); + } else { + WRITE("%d; // %S\n", CS_GEN_DATA(memdata.himem), pair->val); + CSMemoryModel::array_entry(NULL, gen, pair->val, WORD_ARRAY_FORMAT); + } + WRITE("proc.i7_properties["); + Generators::mangle(gen, OUT, owner->name); + WRITE("].len[proc.i7_read_word("); + Generators::mangle(gen, OUT, pair->prop->name); + WRITE(", 1)] = "); + if (pair->inlined) { + WRITE("%S__xt + 1;\n", pair->val); + } else { + WRITE("1;\n"); + } + } + } + OUTDENT; WRITE("}\n"); + CodeGen::deselect(gen, saved); +} + +void CSObjectModel::gather_properties_into_array(code_generation *gen, + CS_property_owner *owner, CS_pv_pair **vals) { + CS_property_owner *super = NULL; + CS_property_owner *co; + LOOP_OVER_LINKED_LIST(co, CS_property_owner, CS_GEN_DATA(objdata.declared_owners)) { + if (Str::eq(co->name, owner->class)) { super = co; break; } + } + if (Str::eq(owner->name, I"Class")) + @; + if (super != owner) CSObjectModel::gather_properties_into_array(gen, super, vals); + CS_pv_pair *pair; + LOOP_OVER_LINKED_LIST(pair, CS_pv_pair, owner->property_values) { + vals[pair->prop->id] = pair; + } +} + +@ The import of this is that because every owner's super-owner's super-owner... +and so on ends in |Class|, and because |Class| provides every either-or property, +it follows that every owner provides every either-or property. And in the absence +of any more specific data, it will be initially |false|. + +This is not true of other properties, which have different runtime semantics. + +@ = + if (CS_GEN_DATA(objdata.Class_either_or_properties_not_set)) { + CS_GEN_DATA(objdata.Class_either_or_properties_not_set) = FALSE; + CS_property *prop; + LOOP_OVER(prop, CS_property) + if (prop->either_or) + CSObjectModel::assign_one_prop(gen, owner, prop, I"0", FALSE); + } + +@h Instances which are not objects. + += +void CSObjectModel::define_constant_for_enumeration(code_generation *gen, + inter_symbol *kind_s, inter_symbol *inst_s, int enumeration) { + TEMPORARY_TEXT(val) + WRITE_TO(val, "%d", enumeration); + Generators::declare_constant(gen, inst_s, RAW_GDCFORM, val); + DISCARD_TEXT(val) +} + +void CSObjectModel::write_vph_identifier(code_generation *gen, OUTPUT_STREAM, + inter_symbol *kind_s) { + WRITE("VPH_%d", VanillaObjects::weak_id(kind_s)); +} + +void CSObjectModel::make_enumerated_property_arrays(code_generation *gen) { + if (CS_GEN_DATA(objdata.value_ranges_needed)) + @; + if (CS_GEN_DATA(objdata.value_property_holders_needed)) + @; +} + +@ This is an array indexed by weak kind ID which holds the largest valid value +for an enumerated kind; or just 0 if the kind is not an enumeration. + +@ = + CSMemoryModel::begin_array(NULL, gen, I"value_ranges", NULL, NULL, WORD_ARRAY_FORMAT, -1, NULL); + CSMemoryModel::array_entry(NULL, gen, I"0", WORD_ARRAY_FORMAT); + inter_symbol *max_weak_id = InterSymbolsTable::URL_to_symbol(gen->from, + I"/main/synoptic/kinds/BASE_KIND_HWM"); + if (max_weak_id) { + int M = InterSymbol::evaluate_to_int(max_weak_id); + for (int w=1; wkinds_in_declaration_order) { + if (VanillaObjects::weak_id(kind_s) == w) { + if (VanillaObjects::value_kind_with_properties(gen, kind_s)) { + written = TRUE; + TEMPORARY_TEXT(N) + WRITE_TO(N, "%d", TypenameInstruction::instance_count(kind_s)); + CSMemoryModel::array_entry(NULL, gen, N, WORD_ARRAY_FORMAT); + DISCARD_TEXT(N) + } + } + } + if (written == FALSE) + CSMemoryModel::array_entry(NULL, gen, I"0", WORD_ARRAY_FORMAT); + } + } + CSMemoryModel::end_array(NULL, gen, WORD_ARRAY_FORMAT, -1, NULL); + +@ This is an array indexed by weak kind ID which holds the object ID of the +value property holder for an enumerated kind; or just 0 if the kind is not an +enumeration. + +@ = + CSMemoryModel::begin_array(NULL, gen, I"value_property_holders", + NULL, NULL, WORD_ARRAY_FORMAT, -1, NULL); + CSMemoryModel::array_entry(NULL, gen, I"0", WORD_ARRAY_FORMAT); + inter_symbol *max_weak_id = InterSymbolsTable::URL_to_symbol(gen->from, + I"/main/synoptic/kinds/BASE_KIND_HWM"); + if (max_weak_id) { + int M = InterSymbol::evaluate_to_int(max_weak_id); + for (int w=1; wkinds_in_declaration_order) { + if (VanillaObjects::weak_id(kind_s) == w) { + if (VanillaObjects::value_kind_with_properties(gen, kind_s)) { + written = TRUE; + TEMPORARY_TEXT(N) + CSObjectModel::write_vph_identifier(gen, N, kind_s); + TEMPORARY_TEXT(M) + Generators::mangle(gen, M, N); + CSMemoryModel::array_entry(NULL, gen, M, WORD_ARRAY_FORMAT); + DISCARD_TEXT(M) + DISCARD_TEXT(N) + } + } + } + if (written == FALSE) + CSMemoryModel::array_entry(NULL, gen, I"0", WORD_ARRAY_FORMAT); + } + } + CSMemoryModel::end_array(NULL, gen, WORD_ARRAY_FORMAT, -1, NULL); + +@h Primitives. +The following primitives are all implemented by calling suitable C functions, +which we will then need to write in |inform7_cslib.h|. + +For |i7_metaclass|, see //CSObjectModel::define_object_value_regions// above. + += +int CSObjectModel::invoke_primitive(code_generation *gen, inter_ti bip, inter_tree_node *P) { + text_stream *OUT = CodeGen::current(gen); + switch (bip) { + case PROPERTYARRAY_BIP: + WRITE("proc.i7_prop_addr("); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(", "); + VNODE_3C; WRITE(")"); break; + case PROPERTYLENGTH_BIP: + WRITE("proc.i7_prop_len("); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(", "); + VNODE_3C; WRITE(")"); break; + case MOVE_BIP: + WRITE("proc.i7_move("); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(")"); break; + case REMOVE_BIP: + WRITE("proc.i7_move("); VNODE_1C; WRITE(", 0)"); break; + case CHILD_BIP: + WRITE("proc.i7_child("); VNODE_1C; WRITE(")"); break; + case CHILDREN_BIP: + WRITE("proc.i7_children("); VNODE_1C; WRITE(")"); break; + case PARENT_BIP: + WRITE("proc.i7_parent("); VNODE_1C; WRITE(")"); break; + case SIBLING_BIP: + WRITE("proc.i7_sibling("); VNODE_1C; WRITE(")"); break; + case METACLASS_BIP: + WRITE("i7_metaclass("); VNODE_1C; WRITE(")"); break; + default: return NOT_APPLICABLE; + } + return FALSE; +} + +@ Let's start with property address and property length; while actual property +values live inside process memory, the addresses showing where they are in that +memory, and how many bytes they take up, are held in (static) arrays. Note that +although multiple processes running the same I7 story would have multiple values +for these properties, which likely differ at any given time, they would be at +the same address and of the same length in each. + +Lengths are returned in bytes, not words, hence the multiplication by 4. + += (text to inform7_cslib.cs) + +class PropertySet { + const int I7_MAX_PROPERTY_IDS = 1000; + + internal readonly int[] address = new int[I7_MAX_PROPERTY_IDS]; + internal readonly int[] len = new int[I7_MAX_PROPERTY_IDS]; +} + +partial class Process { + internal readonly PropertySet[] i7_properties; + + internal int i7_prop_len(int K, int obj, int pr_array) { + int pr = i7_read_word(pr_array, 1); + if ((obj <= 0) || (obj >= i7_max_objects) || + (pr < 0) || (pr >= i7_no_property_ids)) return 0; + return 4*i7_properties[(int) obj].len[(int) pr]; + } + + internal int i7_prop_addr(int K, int obj, int pr_array) { + int pr = i7_read_word(pr_array, 1); + if ((obj <= 0) || (obj >= i7_max_objects) || + (pr < 0) || (pr >= i7_no_property_ids)) return 0; + return i7_properties[(int) obj].address[(int) pr]; + } + +@ The address array can be used to determine whether a runtime object or class +provides a given property: if the address is nonzero then it does. + += (text to inform7_cslib.cs) + internal bool i7_provides(int owner_id, int pr_array) { + int prop_id = i7_read_word(pr_array, 1); + if ((owner_id <= 0) || (owner_id >= i7_max_objects) || + (prop_id < 0) || (prop_id >= i7_no_property_ids)) return false; + while (owner_id != 1) { + if (i7_properties[(int) owner_id].address[(int) prop_id] != 0) return true; + owner_id = story.i7_class_of[owner_id]; + } + return false; + } += + +@ Now |i7_move|, which moves |obj| in the object tree so that it becomes the +eldest child of |to|, unless |to| is zero, in which case it is removed from +the tree. + += (text to inform7_cslib.cs) + internal void i7_move(int obj, int to) { + if ((obj <= 0) || (obj >= i7_max_objects)) return; + int p = state.object_tree_parent[obj]; + if (p != 0) { + if (state.object_tree_child[p] == obj) { + state.object_tree_child[p] = state.object_tree_sibling[obj]; + } else { + int c = state.object_tree_child[p]; + while (c != 0) { + if (state.object_tree_sibling[c] == obj) { + state.object_tree_sibling[c] = state.object_tree_sibling[obj]; + break; + } + c = state.object_tree_sibling[c]; + } + } + } + state.object_tree_parent[obj] = to; + state.object_tree_sibling[obj] = 0; + if (to != 0) { + state.object_tree_sibling[obj] = state.object_tree_child[to]; + state.object_tree_child[to] = obj; + } + } += + +@ Now the four ways to interrogate the object containment tree: + += (text to inform7_cslib.cs) + int i7_parent(int id) { + if (story.i7_metaclass( id) != story.i7_special_class_Object) return 0; + return state.object_tree_parent[id]; + } + int i7_child(int id) { + if (story.i7_metaclass( id) != story.i7_special_class_Object) return 0; + return state.object_tree_child[id]; + } + int i7_children(int id) { + if (story.i7_metaclass( id) != story.i7_special_class_Object) return 0; + int c=0; + for (int i=0; i= i7_max_objects) || + (prop_id < 0) || (prop_id >= i7_no_property_ids)) return 0; + while (i7_properties[(int) owner_id].address[(int) prop_id] == 0) { + owner_id = story.i7_class_of[owner_id]; + if (owner_id == story.i7_special_class_Class) return 0; + } + int address = i7_properties[(int)owner_id].address[(int)prop_id]; + return i7_read_word(address, 0); + } + + void i7_write_prop_value(int owner_id, int pr_array, int val) { + int prop_id = i7_read_word(pr_array, 1); + if ((owner_id <= 0) || (owner_id >= i7_max_objects) || + (prop_id < 0) || (prop_id >= i7_no_property_ids)) { + Console.WriteLine("impossible property write ({0:D}, {1:D})", owner_id, prop_id); + i7_fatal_exit(); + } + int address = i7_properties[(int) owner_id].address[(int) prop_id]; + if (address != 0) i7_write_word(address, 0, val); + else { + Console.WriteLine("impossible property write ({0:D}, {1:D})", owner_id, prop_id); + i7_fatal_exit(); + } + } + + int i7_change_prop_value(int obj, int pr, + int to, int way) { + int val = i7_read_prop_value(obj, pr), new_val = val; + switch (way) { + case i7_lvalue_SET: + i7_write_prop_value(obj, pr, to); new_val = to; break; + case i7_lvalue_PREDEC: + new_val = val-1; i7_write_prop_value(obj, pr, val-1); break; + case i7_lvalue_POSTDEC: + new_val = val; i7_write_prop_value(obj, pr, val-1); break; + case i7_lvalue_PREINC: + new_val = val+1; i7_write_prop_value(obj, pr, val+1); break; + case i7_lvalue_POSTINC: + new_val = val; i7_write_prop_value(obj, pr, val+1); break; + case i7_lvalue_SETBIT: + new_val = val | new_val; i7_write_prop_value(obj, pr, new_val); break; + case i7_lvalue_CLEARBIT: + new_val = val &(~new_val); i7_write_prop_value(obj, pr, new_val); break; + } + return new_val; + } +} += + +@h Reading, writing and changing general properties. +And these are the exactly analogous functions which more generally read, write +or change properties which can be held by either objects or enumerated instances -- +in other words, all properties. The additional kind argument |K| is then needed +to distinguish these cases (since the |obj| values for different kinds may well +coincide). + +The functions themselves are simple enough, but there is a complication, which +is that they need to use addresses which vary from one compilation to another; +so they cannot be written straightforwardly into our C library, which has to +be the same for all compilations. We get around this by compiling wrapper +functions in our story-file C which supply the necessary information and then +call clumsy but static functions in the C library; but this is all transparent +to the user, who should call only these: + +@ So here are the dynamic wrappers. + += +void CSObjectModel::compile_gprop_functions(code_generation *gen) { + segmentation_pos saved = CodeGen::select(gen, cs_function_declarations_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("//#if i7_mgl_OBJECT_TY\n"); + WRITE("int i7_provides_gprop(Inform.Process proc, int K, int obj, int p) {\n"); + WRITE(" return System.Convert.ToInt32(proc.i7_provides_gprop_inner(K, obj, p, i7_mgl_OBJECT_TY,\n"); + WRITE(" i7_mgl_value_ranges, i7_mgl_value_property_holders, i7_mgl_COL_HSIZE));\n"); + WRITE("}\n"); + WRITE("int i7_read_gprop_value(Inform.Process proc, int K, int obj, int p) {\n"); + WRITE(" return proc.i7_read_gprop_value_inner(K, obj, p, i7_mgl_OBJECT_TY,\n"); + WRITE(" i7_mgl_value_ranges, i7_mgl_value_property_holders, i7_mgl_COL_HSIZE);\n"); + WRITE("}\n"); + WRITE("void i7_write_gprop_value(Inform.Process proc, int K, int obj,\n"); + WRITE(" int p, int val) {\n"); + WRITE(" proc.i7_write_gprop_value_inner(K, obj, p, val, i7_mgl_OBJECT_TY,\n"); + WRITE(" i7_mgl_value_ranges, i7_mgl_value_property_holders, i7_mgl_COL_HSIZE);\n"); + WRITE("}\n"); + WRITE("void i7_change_gprop_value(Inform.Process proc, int K, int obj,\n"); + WRITE(" int p, int val, int form) {\n"); + WRITE(" proc.i7_change_gprop_value_inner(K, obj, p, val, form, i7_mgl_OBJECT_TY,\n"); + WRITE(" i7_mgl_value_ranges, i7_mgl_value_property_holders, i7_mgl_COL_HSIZE);\n"); + WRITE("}\n"); + WRITE("//#endif\n"); + CodeGen::deselect(gen, saved); +} + +@ And these are the static functions in the C library which they call: + += (text to inform7_cslib.cs) +partial class Process { + internal bool i7_provides_gprop_inner(int K, int obj, int pr, + int i7_mgl_OBJECT_TY, int i7_mgl_value_ranges, + int i7_mgl_value_property_holders, int i7_mgl_COL_HSIZE) { + if (K == i7_mgl_OBJECT_TY) { + if ((((obj != 0) && ((story.i7_metaclass( obj) == story.i7_special_class_Object)))) && + (((i7_read_word(pr, 0) == 2) || (i7_provides(obj, pr))))) + return true; + } else { + if ((((obj >= 1)) && ((obj <= i7_read_word(i7_mgl_value_ranges, K))))) { + int holder = i7_read_word(i7_mgl_value_property_holders, K); + if (((holder !=0) && ((i7_provides(holder, pr))))) return true; + } + } + return false; + } + + internal int i7_read_gprop_value_inner(int K, int obj, int pr, + int i7_mgl_OBJECT_TY, int i7_mgl_value_ranges, + int i7_mgl_value_property_holders, int i7_mgl_COL_HSIZE) { + int val = 0; + if ((K == i7_mgl_OBJECT_TY)) { + return (int) i7_read_prop_value(obj, pr); + } else { + int holder = i7_read_word(i7_mgl_value_property_holders, K); + return (int) i7_read_word( + i7_read_prop_value(holder, pr), (obj + i7_mgl_COL_HSIZE)); + } + return val; + } + + internal void i7_write_gprop_value_inner(int K, int obj, int pr, + int val, int i7_mgl_OBJECT_TY, int i7_mgl_value_ranges, + int i7_mgl_value_property_holders, int i7_mgl_COL_HSIZE) { + if ((K == i7_mgl_OBJECT_TY)) { + i7_write_prop_value(obj, pr, val); + } else { + int holder = i7_read_word(i7_mgl_value_property_holders, K); + i7_write_word( + i7_read_prop_value(holder, pr), (obj + i7_mgl_COL_HSIZE), val); + } + } + + internal void i7_change_gprop_value_inner(int K, int obj, int pr, + int val, int form, + int i7_mgl_OBJECT_TY, int i7_mgl_value_ranges, + int i7_mgl_value_property_holders, int i7_mgl_COL_HSIZE) { + if ((K == i7_mgl_OBJECT_TY)) { + i7_change_prop_value(obj, pr, val, form); + } else { + int holder = i7_read_word(i7_mgl_value_property_holders, K); + i7_change_word( + i7_read_prop_value(holder, pr), (obj + i7_mgl_COL_HSIZE), val, form); + } + } +} += diff --git a/inter/final-module/Chapter 6/C# Program Control.w b/inter/final-module/Chapter 6/C# Program Control.w new file mode 100644 index 0000000000..476451b765 --- /dev/null +++ b/inter/final-module/Chapter 6/C# Program Control.w @@ -0,0 +1,186 @@ +[CSProgramControl::] C# Program Control. + +Generating C# code to effect loops, branches and the like. + +@ This is as good a place as any to provide the general function for compiling +invocations of primitives. There are a lot of primitives, so the actual work is +distributed throughout this chapter. + += +void CSProgramControl::initialise(code_generator *gtr) { + METHOD_ADD(gtr, INVOKE_PRIMITIVE_MTID, CSProgramControl::invoke_primitive); +} + +void CSProgramControl::invoke_primitive(code_generator *gtr, code_generation *gen, + inter_symbol *prim_name, inter_tree_node *P, int void_context) { + inter_tree *I = gen->from; + inter_ti bip = Primitives::to_BIP(I, prim_name); + + int r = CSReferences::invoke_primitive(gen, bip, P); + if (r == NOT_APPLICABLE) r = CSArithmetic::invoke_primitive(gen, bip, P); + if (r == NOT_APPLICABLE) r = CSMemoryModel::invoke_primitive(gen, bip, P); + if (r == NOT_APPLICABLE) r = CSFunctionModel::invoke_primitive(gen, bip, P); + if (r == NOT_APPLICABLE) r = CSObjectModel::invoke_primitive(gen, bip, P); + if (r == NOT_APPLICABLE) r = CSInputOutputModel::invoke_primitive(gen, bip, P); + if (r == NOT_APPLICABLE) r = CSConditions::invoke_primitive(gen, bip, P); + if (r == NOT_APPLICABLE) r = CSProgramControl::compile_control_primitive(gen, bip, P); + if ((void_context) && (r == FALSE)) { + text_stream *OUT = CodeGen::current(gen); + WRITE(";\n"); + } +} + +@ And the rest of this section implements the primitives to do with execution +control: branches, loops and so on. + += +int CSProgramControl::compile_control_primitive(code_generation *gen, inter_ti bip, + inter_tree_node *P) { + int suppress_terminal_semicolon = FALSE; + text_stream *OUT = CodeGen::current(gen); + inter_tree *I = gen->from; + switch (bip) { + case PUSH_BIP: WRITE("proc.i7_push("); VNODE_1C; WRITE(")"); break; + case PULL_BIP: VNODE_1C; WRITE(" = proc.i7_pull()"); break; + case IF_BIP: @; break; + case IFDEBUG_BIP: @; break; + case IFSTRICT_BIP: @; break; + case IFELSE_BIP: @; break; + case BREAK_BIP: WRITE("break"); break; + case CONTINUE_BIP: WRITE("continue"); break; + case JUMP_BIP: WRITE("goto "); VNODE_1C; break; + case QUIT_BIP: WRITE("proc.i7_benign_exit()"); break; + case RESTORE_BIP: WRITE("proc.i7_opcode_restore(0, NULL)"); break; + case RETURN_BIP: WRITE("return System.Convert.ToInt32("); VNODE_1C; WRITE(")"); break; + case WHILE_BIP: @; break; + case DO_BIP: @; break; + case FOR_BIP: @; break; + case OBJECTLOOP_BIP: @; break; + case OBJECTLOOPX_BIP: @; break; + case SWITCH_BIP: @; break; + case CASE_BIP: @; break; + case DEFAULT_BIP: @; break; + case ALTERNATIVECASE_BIP: internal_error("misplaced !alternativecase"); break; + default: internal_error("unimplemented prim"); + } + return suppress_terminal_semicolon; +} + +@ = + WRITE("if /*pi*/(System.Convert.ToBoolean("); VNODE_1C; WRITE(")) {\n"); INDENT; VNODE_2C; + OUTDENT; WRITE("}\n"); + suppress_terminal_semicolon = TRUE; + +@ = + WRITE("#if DEBUG\n"); INDENT; VNODE_1C; OUTDENT; WRITE("#endif\n"); + suppress_terminal_semicolon = TRUE; + +@ = + WRITE("#if STRICT_MODE\n"); INDENT; VNODE_1C; OUTDENT; WRITE("#endif\n"); + suppress_terminal_semicolon = TRUE; + +@ = + WRITE("if /*pie*/(System.Convert.ToBoolean("); VNODE_1C; WRITE(")) {\n"); INDENT; VNODE_2C; OUTDENT; + WRITE("} else {\n"); INDENT; VNODE_3C; OUTDENT; WRITE("}\n"); + suppress_terminal_semicolon = TRUE; + +@ = + WRITE("while (System.Convert.ToBoolean("); VNODE_1C; WRITE(")) {\n"); INDENT; VNODE_2C; OUTDENT; WRITE("}\n"); + suppress_terminal_semicolon = TRUE; + +@ = + WRITE("do {"); VNODE_2C; WRITE("} while (!System.Convert.ToBoolean(\n"); INDENT; VNODE_1C; OUTDENT; WRITE("))\n"); + +@ = + WRITE("for ("); + inter_tree_node *INIT = InterTree::first_child(P); + if (!((Inode::is(INIT, VAL_IST)) && + (InterValuePairs::is_number(ValInstruction::value(INIT))) && + (InterValuePairs::to_number(ValInstruction::value(INIT)) == 1))) + VNODE_1C; + WRITE(";System.Convert.ToBoolean("); VNODE_2C; + WRITE(");"); + inter_tree_node *U = InterTree::third_child(P); + if (Inode::isnt(U, VAL_IST)) + Vanilla::node(gen, U); + WRITE(") {\n"); INDENT; VNODE_4C; + OUTDENT; WRITE("}\n"); + suppress_terminal_semicolon = TRUE; + +@ = + int in_flag = FALSE; + inter_tree_node *U = InterTree::third_child(P); + if ((Inode::is(U, INV_IST)) && + (InvInstruction::method(U) == PRIMITIVE_INVMETH)) { + inter_symbol *prim = InvInstruction::primitive(U); + if ((prim) && (Primitives::to_BIP(I, prim) == IN_BIP)) in_flag = TRUE; + } + + WRITE("for ("); VNODE_1C; + WRITE(" = 1; "); VNODE_1C; + WRITE(" < i7_max_objects; "); VNODE_1C; + WRITE("++) "); + if (in_flag == FALSE) { + WRITE("if (System.Convert.ToBoolean(proc.i7_ofclass("); VNODE_1C; WRITE(", "); VNODE_2C; WRITE("))) "); + } + WRITE("if /*ol*/(System.Convert.ToBoolean("); + VNODE_3C; + WRITE(")) {\n"); INDENT; VNODE_4C; + OUTDENT; WRITE("}\n"); + suppress_terminal_semicolon = TRUE; + +@ = + WRITE("for ("); VNODE_1C; + WRITE(" = 1; "); VNODE_1C; + WRITE(" < i7_max_objects; "); VNODE_1C; + WRITE("++) "); + WRITE("if (System.Convert.ToBoolean(proc.i7_ofclass("); VNODE_1C; WRITE(", "); VNODE_2C; WRITE("))) "); + WRITE(" {\n"); INDENT; VNODE_3C; + OUTDENT; WRITE("}\n"); + suppress_terminal_semicolon = TRUE; + +@ = + WRITE("switch ("); VNODE_1C; + WRITE(") {\n"); INDENT; VNODE_2C; OUTDENT; WRITE("}\n"); + suppress_terminal_semicolon = TRUE; + +@ TODO Inter permits multiple match values to be supplied for a single case in a +|!switch| primitive: but C does not allow this for its keyword |case|, so we +have to recurse downwards through the possibilities and preface each one by +|case:|. For example, += (text as Inter) + inv !switch + inv !alternativecase + val K_number 3 + val K_number 7 + ... += +becomes |case 3: case 7:|. + +@ = + CSProgramControl::caser(gen, InterTree::first_child(P)); + INDENT; VNODE_2C; WRITE(";\n"); WRITE("break;\n"); OUTDENT; + suppress_terminal_semicolon = TRUE; + +@ = +void CSProgramControl::caser(code_generation *gen, inter_tree_node *X) { + if (Inode::is(X, INV_IST)) { + if (InvInstruction::method(X) == PRIMITIVE_INVMETH) { + inter_symbol *prim = InvInstruction::primitive(X); + inter_ti xbip = Primitives::to_BIP(gen->from, prim); + if (xbip == ALTERNATIVECASE_BIP) { + CSProgramControl::caser(gen, InterTree::first_child(X)); + CSProgramControl::caser(gen, InterTree::second_child(X)); + return; + } + } + } + text_stream *OUT = CodeGen::current(gen); + WRITE("case "); + Vanilla::node(gen, X); + WRITE(": "); +} + +@ = + WRITE("default:\n"); INDENT; VNODE_1C; WRITE(";\n"); WRITE("break;\n"); OUTDENT; + suppress_terminal_semicolon = TRUE; diff --git a/inter/final-module/Chapter 6/C# References.w b/inter/final-module/Chapter 6/C# References.w new file mode 100644 index 0000000000..0b198a0984 --- /dev/null +++ b/inter/final-module/Chapter 6/C# References.w @@ -0,0 +1,118 @@ +[CSReferences::] C# References. + +How changes to storage objects are translated into C#. + +@ References identify storage objects which are being written to or otherwise +modified, rather than having their current contents read. + +There are seven possible ways to modify something identified by a reference, +and we need constants to identify these ways in the code we generate: + += (text to inform7_cslib.cs) +partial class Process { + public const int i7_lvalue_SET = 1; + public const int i7_lvalue_PREDEC = 2; + public const int i7_lvalue_POSTDEC = 3; + public const int i7_lvalue_PREINC = 4; + public const int i7_lvalue_POSTINC = 5; + public const int i7_lvalue_SETBIT = 6; + public const int i7_lvalue_CLEARBIT = 7; +} += + +@ Those seven ways correspond to seven Inter primitives, with the following +signatures: += (text) +primitive !store ref val -> val +primitive !preincrement ref -> val +primitive !postincrement ref -> val +primitive !predecrement ref -> val +primitive !postdecrement ref -> val +primitive !setbit ref val -> void +primitive !clearbit ref val -> void += +Since C# functions can have their return values freely ignored, we will in fact +implement |!setbit| and |!clearbit| as if they too had the signature +|ref val -> val|. + += +int CSReferences::invoke_primitive(code_generation *gen, inter_ti bip, inter_tree_node *P) { + text_stream *OUT = CodeGen::current(gen); + text_stream *store_form = NULL; + switch (bip) { + case STORE_BIP: store_form = I"Inform.Process.i7_lvalue_SET"; break; + case PREINCREMENT_BIP: store_form = I"Inform.Process.i7_lvalue_PREINC"; break; + case POSTINCREMENT_BIP: store_form = I"Inform.Process.i7_lvalue_POSTINC"; break; + case PREDECREMENT_BIP: store_form = I"Inform.Process.i7_lvalue_PREDEC"; break; + case POSTDECREMENT_BIP: store_form = I"Inform.Process.i7_lvalue_POSTDEC"; break; + case SETBIT_BIP: store_form = I"Inform.Process.i7_lvalue_SETBIT"; break; + case CLEARBIT_BIP: store_form = I"Inform.Process.i7_lvalue_CLEARBIT"; break; + default: return NOT_APPLICABLE; + } + if (store_form) @; + return FALSE; +} + +@ Some storage objects, like variables, can be generated to C# code which works +in either an lvalue or rvalue context. For example, the Inter variable |frog| +generates just as the C# variable |i7_mgl_frog|.[1] It's then fine to generate +code like either |10 + i7_mgl_frog|, where it is used in a |val| context, or +like |i7_mgl_frog++|, where it is used in a |ref| context. + +But other storage objects are not so lucky, and can only be written to by +calling functions. + +[1] In real life, do not mangle frogs. See C. S. Lewis, "Perelandra", 1943. + +@ = + WRITE("/*tdimavbr*/"); + inter_tree_node *ref = InterTree::first_child(P); + inter_tree_node *storage_ref = InterTree::first_child(P); + if (storage_ref->W.instruction[0] == REFERENCE_IST) + storage_ref = InterTree::first_child(storage_ref); + int val_supplied = FALSE; + if ((bip == STORE_BIP) || (bip == SETBIT_BIP) || (bip == CLEARBIT_BIP)) val_supplied = TRUE; + if (ReferenceInstruction::node_is_ref_to(gen->from, ref, LOOKUP_BIP)) + @ + else if (ReferenceInstruction::node_is_ref_to(gen->from, ref, LOOKUPBYTE_BIP)) + @ + else if (ReferenceInstruction::node_is_ref_to(gen->from, ref, PROPERTYVALUE_BIP)) + @ + else + @; + +@ = + WRITE("proc.i7_change_word("); + Vanilla::node(gen, InterTree::first_child(storage_ref)); WRITE(", "); + Vanilla::node(gen, InterTree::second_child(storage_ref)); WRITE(", "); + if (val_supplied) { WRITE("unchecked((short)"); VNODE_2C; WRITE(")"); } else { WRITE("0"); } + WRITE(", %S", store_form); + WRITE(")"); + +@ = + WRITE("proc.i7_change_byte("); + Vanilla::node(gen, InterTree::first_child(storage_ref)); WRITE(" + "); + Vanilla::node(gen, InterTree::second_child(storage_ref)); WRITE(", "); + if (val_supplied) { WRITE("(byte)"); VNODE_2C; } else { WRITE("0"); } + WRITE(", %S", store_form); + WRITE(")"); + +@ = + WRITE("i7_change_gprop_value(proc, "); + Vanilla::node(gen, InterTree::first_child(storage_ref)); WRITE(", "); + Vanilla::node(gen, InterTree::second_child(storage_ref)); WRITE(", "); + Vanilla::node(gen, InterTree::third_child(storage_ref)); WRITE(", "); + if (val_supplied) { VNODE_2C; } else { WRITE("0"); } + WRITE(", %S", store_form); + WRITE(")"); + +@ = + switch (bip) { + case PREINCREMENT_BIP: WRITE("++("); VNODE_1C; WRITE(")"); break; + case POSTINCREMENT_BIP: WRITE("("); VNODE_1C; WRITE(")++"); break; + case PREDECREMENT_BIP: WRITE("--("); VNODE_1C; WRITE(")"); break; + case POSTDECREMENT_BIP: WRITE("("); VNODE_1C; WRITE(")--"); break; + case STORE_BIP: VNODE_1C; WRITE(" = "); VNODE_2C; break; + case SETBIT_BIP: VNODE_1C; WRITE(" = "); VNODE_1C; WRITE(" | "); VNODE_2C; break; + case CLEARBIT_BIP: VNODE_1C; WRITE(" = "); VNODE_1C; WRITE(" &~ ("); VNODE_2C; WRITE(")"); break; + } diff --git a/inter/final-module/Chapter 6/C# Utility Functions.w b/inter/final-module/Chapter 6/C# Utility Functions.w new file mode 100644 index 0000000000..88727ff75a --- /dev/null +++ b/inter/final-module/Chapter 6/C# Utility Functions.w @@ -0,0 +1,131 @@ +[CSUtilities::] C# Utility Functions. + +Rounding out the C# library with a few functions intended for external code to use. + +@ We will frequently need to reinterpret |int| values as |float|, +or vice versa. The following functions must be perfect inverses of each other. + += (text to inform7_cslib.cs) +partial class Process { + static int i7_encode_float(float val) { + return BitConverter.SingleToInt32Bits(val); + } + + static float i7_decode_float(int val) { + return BitConverter.Int32BitsToSingle(val); + } += + +@ These two functions allow external C# code to read to, or write from, an +Inform 7 variable inside a currently running process. + + += (text to inform7_cslib.cs) + int i7_read_variable(int var_id) { + return state.variables[var_id]; + } + void i7_write_variable(int var_id, int val) { + state.variables[var_id] = val; + } + += + +@ Text values extracted from such variables would be difficult to interpret +from the outside because of the complex way in which text is stored within an +Inform 7 process, so the following functions allow text inside the process +to be converted to or from null-terminated C strings. + += (text to inform7_cslib.cs) + string i7_read_string(int S) { + #if i7_mgl_BASICINFORMKIT + i7_fn_TEXT_TY_Transmute(proc, S); + int L = i7_fn_TEXT_TY_CharacterLength(proc, S, 0, 0, 0, 0, 0, 0); + string A = malloc(L + 1); + if (A == NULL) { + Console.Error.WriteLine("Out of memory"); i7_fatal_exit(); + } + for (int i=0; igenerator_private_data))->x + += +typedef struct CS_generation_data { + int compile_main; + int compile_symbols; + struct dictionary *symbols_header_identifiers; + struct CS_generation_assembly_data asmdata; + struct CS_generation_memory_model_data memdata; + struct CS_generation_function_model_data fndata; + struct CS_generation_object_model_data objdata; + struct CS_generation_literals_model_data litdata; + struct CS_generation_variables_data vardata; + CLASS_DEFINITION +} CS_generation_data; + +void CSTarget::initialise_data(code_generation *gen) { + CS_GEN_DATA(compile_main) = TRUE; + CS_GEN_DATA(compile_symbols) = FALSE; + CS_GEN_DATA(symbols_header_identifiers) = Dictionaries::new(1024, TRUE); + CSMemoryModel::initialise_data(gen); + CSFunctionModel::initialise_data(gen); + CSObjectModel::initialise_data(gen); + CSLiteralsModel::initialise_data(gen); + CSGlobals::initialise_data(gen); + CSAssembly::initialise_data(gen); + CSInputOutputModel::initialise_data(gen); +} + +@h Begin and end. +We return |FALSE| here to signal that we want the Vanilla algorithm to +manage the process. + += +int CSTarget::begin_generation(code_generator *gtr, code_generation *gen) { + CodeGen::create_segments(gen, CREATE(CS_generation_data), CS_target_segments); + CodeGen::additional_segments(gen, CS_symbols_header_segments); + CSTarget::initialise_data(gen); + + @; + @; + @; + + CSNamespace::fix_locals(gen); + CSMemoryModel::begin(gen); + CSFunctionModel::begin(gen); + CSObjectModel::begin(gen); + CSLiteralsModel::begin(gen); + CSGlobals::begin(gen); + CSAssembly::begin(gen); + CSInputOutputModel::begin(gen); + return FALSE; +} + +@ = + linked_list *opts = TargetVMs::option_list(gen->for_VM); + text_stream *opt; + LOOP_OVER_LINKED_LIST(opt, text_stream, opts) { + if (Str::eq_insensitive(opt, I"main")) CS_GEN_DATA(compile_main) = TRUE; + else if (Str::eq_insensitive(opt, I"no-main")) CS_GEN_DATA(compile_main) = FALSE; + else if (Str::eq_insensitive(opt, I"symbols-header")) CS_GEN_DATA(compile_symbols) = TRUE; + else if (Str::eq_insensitive(opt, I"no-symbols-header")) CS_GEN_DATA(compile_symbols) = FALSE; + else { + #ifdef PROBLEMS_MODULE + Problems::fatal("Unknown compilation format option"); + #endif + #ifndef PROBLEMS_MODULE + Errors::fatal("Unknown compilation format option"); + exit(1); + #endif + } + } + +@ = + segmentation_pos saved = CodeGen::select(gen, cs_header_inclusion_I7CGS); + text_stream *OUT = CodeGen::current(gen); + if (Architectures::debug_enabled(TargetVMs::get_architecture(gen->for_VM))) + WRITE("#define DEBUG\n"); + WRITE("public class i7 : Inform.Story {\n"); + if (CS_GEN_DATA(compile_main)) + WRITE("static void Main(string[] args) { Inform.Defaults.i7_default_main(args, new i7()); }\n"); + WRITE("/*#pragma clang diagnostic push\n"); + WRITE("#pragma clang diagnostic ignored \"-Wunused-value\"\n"); + WRITE("#pragma clang diagnostic ignored \"-Wparentheses-equality\"*/\n"); + CodeGen::deselect(gen, saved); + saved = CodeGen::select(gen, cs_constructor_I7CGS); + OUT = CodeGen::current(gen); + WRITE("\tpublic i7() {\n"); + CodeGen::deselect(gen, saved); + +@ = + if (gen->dictionary_resolution < 0) { + segmentation_pos saved = CodeGen::select(gen, cs_ids_and_maxima_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("const int "); + CNamespace::mangle(gtr, OUT, I"DICT_WORD_SIZE"); + WRITE(" = 9;\n"); + CodeGen::deselect(gen, saved); + } + + +@ The Inform 6 compiler automatically generates the dictionary, verb and actions +tables, but other compilers do not, of course, so generators for other languages +(such as this one) must ask Vanilla to make those tables for it. + += +int CSTarget::end_generation(code_generator *gtr, code_generation *gen) { + VanillaIF::compile_dictionary_table(gen); + VanillaIF::compile_verb_table(gen); + VanillaIF::compile_actions_table(gen); + CSFunctionModel::end(gen); + CSObjectModel::end(gen); + CSLiteralsModel::end(gen); + CSGlobals::end(gen); + CSAssembly::end(gen); + CSInputOutputModel::end(gen); + CSMemoryModel::end(gen); /* must be last to end */ + @; + filename *F = gen->to_file; + if ((F) && (CS_GEN_DATA(compile_symbols))) @; + return FALSE; +} + +@ = + segmentation_pos saved = CodeGen::select(gen, cs_constructor_I7CGS); + text_stream *OUT = CodeGen::current(gen); + WRITE("\t}\n}\n"); + CodeGen::deselect(gen, saved); + +@ = + filename *G = Filenames::in(Filenames::up(F), I"inform7_symbols.h"); + text_stream HF; + if (STREAM_OPEN_TO_FILE(&HF, G, ISO_ENC) == FALSE) { + #ifdef PROBLEMS_MODULE + Problems::fatal_on_file("Can't open output file", G); + #endif + #ifndef PROBLEMS_MODULE + Errors::fatal_with_file("Can't open output file", G); + exit(1); + #endif + } + WRITE_TO(&HF, "/* Symbols derived mechanically from Inform 7 source: do not edit */\n\n"); + WRITE_TO(&HF, "/* (1) Instance IDs */\n\n"); + CodeGen::write_segment(&HF, gen->segmentation.segments[cs_instances_symbols_I7CGS]); + WRITE_TO(&HF, "\n/* (2) Values of enumerated kinds */\n\n"); + CodeGen::write_segment(&HF, gen->segmentation.segments[cs_enum_symbols_I7CGS]); + WRITE_TO(&HF, "\n/* (3) Kind IDs */\n\n"); + CodeGen::write_segment(&HF, gen->segmentation.segments[cs_kinds_symbols_I7CGS]); + WRITE_TO(&HF, "\n/* (4) Action IDs */\n\n"); + CodeGen::write_segment(&HF, gen->segmentation.segments[cs_actions_symbols_I7CGS]); + WRITE_TO(&HF, "\n/* (5) Property IDs */\n\n"); + CodeGen::write_segment(&HF, gen->segmentation.segments[cs_property_symbols_I7CGS]); + WRITE_TO(&HF, "\n/* (6) Variable IDs */\n\n"); + CodeGen::write_segment(&HF, gen->segmentation.segments[cs_variable_symbols_I7CGS]); + WRITE_TO(&HF, "\n/* (7) Function IDs */\n\n"); + CodeGen::write_segment(&HF, gen->segmentation.segments[cs_function_symbols_I7CGS]); + STREAM_CLOSE(&HF); + +@ When defining constants to be defined in the symbols header, the following +function is a convenience, automatically ensuring that names never clash: + += +text_stream *CSTarget::symbols_header_identifier(code_generation *gen, + text_stream *prefix, text_stream *raw) { + dictionary *D = CS_GEN_DATA(symbols_header_identifiers); + text_stream *key = Str::new(); + WRITE_TO(key, "i7_%S_", prefix); + LOOP_THROUGH_TEXT(pos, raw) + if (Characters::isalnum(Str::get(pos))) + PUT_TO(key, Str::get(pos)); + else + PUT_TO(key, '_'); + text_stream *dv = Dictionaries::get_text(D, key); + if (dv) { + TEMPORARY_TEXT(keyx) + int n = 2; + while (TRUE) { + Str::clear(keyx); + WRITE_TO(keyx, "%S_%d", key, n); + if (Dictionaries::get_text(D, keyx) == NULL) break; + n++; + } + DISCARD_TEXT(keyx) + WRITE_TO(key, "_%d", n); + } + Dictionaries::create_text(D, key); + return key; +} + +@h Static supporting code. +The C# code generated here would not compile as a stand-alone file. It needs +to use variables and functions from a small unchanging library called +|inform7_cslib.cs|, which TODO has an associated header file |inform7_cslib.h| of +declarations so that code can be linked to it. (See the test makes |Eg1-CS|, +|Eg2-CS| and so on for demonstrations of this.) + +Those two files are presented throughout this chapter, because the implementation +of the I7 C# library is so closely tied to the code we compile: they can only +really be understood jointly. + +And similarly for |inform7_cslib.cs|: + += (text to inform7_cslib.cs) +/* This is a library of C# code to support Inter code compiled to C#. It was + generated mechanically from the Inter source code, so to change this material, + edit that and not this file. */ + +using System; +using System.IO; + +namespace Inform { += + +@ TODO Now we need four fundamental types. |int| is a type which can hold any +Inter word value: since we do not support C# for 16-bit Inter code, we can +safely make this a 32-bit integer. |unsigned_int| will be used very little, +but is an unsigned version of the same. (It must be the case that an |int| +can survive being cast to |unsigned_int| and back again intact.) + +|byte| holds an Inter byte value, and must be unsigned. + +It must unfortunately be the case that |float| values can be stored in +|int| containers at runtime, which is why they are only |float| and not +|double| precision. + +Our library is going to be able to manage multiple independently-running +"processes", storage for each of which is a single |Process| structure. +Within that, the current execution state is an |State|, which we now define. + +The most important thing here is |memory|: the byte-addressable space +holding arrays and property values. (Note that, unlike the memory architecture +for the Z-machine or Glulx VMs, this memory space contains no program code. If +the same Inter code is compiled once to C, and then also to Glulx via Inform 6, +there will be some similarities between the C |memory| contents and the RAM-like +parts of the Glulx VM's memory, but only some. Addresses will be quite different +between the two.) + +The valid range of memory addresses is between 0 and |himem| minus 1. + +There is also a stack, but only a small one. Note that this does not contain +return addresses, in the way a traditional stack might: it simply holds values +which have explicitly been pushed by the Inter opcode |@push| and not yet pulled. +It doesn't live in memory, and cannot otherwise be read or written by the Inter +program; it is empty when |stack_pointer| is zero. + +The object containment tree is also stored outside of memory; that's a choice +on our part, and makes it slightly faster to access. The same applies to the +array of global |variables|. (Again, this is a point of difference with the +traditional IF virtual machines, which put all of this in memory.) + +The temporary value |tmp| holds data only fleetingly, during the execution of +a single Inter primitive or assembly opcode. + += (text to inform7_cslib.cs) +struct RngSeed { + internal uint A; + internal uint interval; + internal uint counter; + + public static RngSeed i7_initial_rng_seed() { + RngSeed seed = new RngSeed(); + seed.A = 1; + return seed; + } +} + +class State { + internal const int I7_ASM_STACK_CAPACITY = 128; + internal const int I7_TMP_STORAGE_CAPACITY = 128; + + internal byte[] memory; + internal int himem; + internal readonly int[] stack = new int[I7_ASM_STACK_CAPACITY]; + internal int stack_pointer; + internal int[] object_tree_parent; + internal int[] object_tree_child; + internal int[] object_tree_sibling; + internal int[] variables; + internal readonly int[] tmp = new int[I7_TMP_STORAGE_CAPACITY]; + internal int current_output_stream_ID; + internal RngSeed seed = RngSeed.i7_initial_rng_seed(); +} += + +A "snapshot" is basically a saved state. += (text to inform7_cslib.cs) +class Snapshot { + internal bool valid; + internal State then; + + internal Snapshot() { + then = new State(); + } +} += + +Okay then: a "process". This contains not only the current state but snapshots +of 10 recent states, in order to facilitate the UNDO operation. Snapshots are +stored in a form of ring buffer, to avoid ever copying them in memory. += (text to inform7_cslib.cs) +public partial class Process { + const int I7_MAX_SNAPSHOTS = 10; + + readonly Story story; + internal State state = new State(); + Snapshot[] snapshots = new Snapshot[I7_MAX_SNAPSHOTS]; + int snapshot_pos; + public int termination_code; + public Action receiver = Defaults.i7_default_receiver; + int send_count; + public Func sender = Defaults.i7_default_sender; + public Action stylist = Defaults.i7_default_stylist; + public Func glk_implementation = Defaults.i7_default_glk; + internal MiniGlkData miniglk; + int /*TODO: bool */ use_UTF8 = 1; + + public Process(Story story) { + this.story = story; + i7_max_objects = story.i7_max_objects; + for (int i=0; i receiver, int UTF8) { + this.receiver = receiver; + use_UTF8 = UTF8; + } + public void i7_set_process_sender(Func sender) { + this.sender = sender; + } += + +Similarly, ambitious projects which want their own complete I/O systems can +set the following: + += (text to inform7_cslib.cs) + public void i7_set_process_stylist(Action stylist) { + this.stylist = stylist; + } + public void i7_set_process_glk_implementation(Func glk_implementation) { + this.glk_implementation = glk_implementation; + } +} += + +In all cases, execution is kicked off when |i7_run_process| is called on a process. +Ordinarily, that will execute the entire Inform 7 program and then come back to us; +but we need to cope with a sudden halt during execution, either through a fatal +error or through a use of the |@quit| opcode. + +We do that with the |setjmp| and |longjmp| mechanism of C. This is a very limited +sort of exception-handling will a well deserved reputation for crankiness, and we +will use it with due caution. It is essential that the underlying |jmp_buf| data +not move in memory for any reason between the setting and the jumping. (This is +why there is no mechanism to copy or fork an |i7_process_t|.) + +Note that the |i7_initialiser| function is compiled and is not pre-written +like these other functions: see //C# Object Model// for what it does. + += (text to inform7_cslib.cs) +class ProcessTerminationException : Exception { + public int return_code; + public ProcessTerminationException(int returnCode) => this.return_code = returnCode; +} + +partial class Story { + public abstract int i7_fn_Main(Process proc); +} + +partial class Process { + public int i7_run_process() { + try { + i7_initialise_memory_and_stack(); + i7_initialise_variables(); + i7_empty_object_tree(); + story.i7_initialiser(this); + story.i7_initialise_object_tree(this); + story.i7_fn_Main(this); + termination_code = 0; /* terminated because the program completed */ + } catch (ProcessTerminationException termination) { + termination_code = termination.return_code; /* terminated mid-stream */ + } + return termination_code; + } + + public void i7_fatal_exit() { + // Uncomment the next line to force a crash so that the stack can be inspected in a debugger + // int x = 0; Console.Write("{0:D}", 1/x); + throw new ProcessTerminationException(1); + } + + public void i7_benign_exit() { + throw new ProcessTerminationException(0); + } +} += diff --git a/inter/final-module/Contents.w b/inter/final-module/Contents.w index cd9b57458b..0a3a9928e9 100644 --- a/inter/final-module/Contents.w +++ b/inter/final-module/Contents.w @@ -48,3 +48,20 @@ Chapter 5: C C Input-Output Model C Miniglk C Utility Functions + +Chapter 6: C# + Final C# + C# Namespace + C# References + C# Global Variables + C# Memory Model + C# Assembly + C# Arithmetic + C# Program Control + C# Conditions + C# Literals + C# Object Model + C# Function Model + C# Input-Output Model + C# Miniglk + C# Utility Functions diff --git a/scripts/inform.mkscript b/scripts/inform.mkscript index 9ec252e3c0..664e62e538 100644 --- a/scripts/inform.mkscript +++ b/scripts/inform.mkscript @@ -192,11 +192,13 @@ SENGLISHPATH = $(INFORM7WEB)/Internal/Extensions/Graham Nelson/English Language. localintegration: \ $(ENGLISHPATH)/Syntax.preform \ $(INFORM7WEB)/Internal/Miscellany/inform7_clib.h \ - $(INFORM7WEB)/Internal/Miscellany/inform7_clib.c + $(INFORM7WEB)/Internal/Miscellany/inform7_clib.c \ + $(INFORM7WEB)/Internal/Miscellany/inform7_cslib.cs # When tangled, the inform7 web most importantly makes inform7.c, of course, -# but it makes three side files as well: Syntax.preform, a grammar file, and -# a small C library used when compiling Inform projects to C. +# but it makes four side files as well: Syntax.preform, a grammar file, +# a small C library used when compiling Inform projects to C, and a similar +# one for C#. $(ENGLISHPATH)/Syntax.preform: $(INFORM7WEB)/Tangled/Syntax.preform cp -f "$(INFORM7WEB)/Tangled/Syntax.preform" "$(SENGLISHPATH)/Syntax.preform" @@ -207,8 +209,11 @@ $(INFORM7WEB)/Internal/Miscellany/inform7_clib.h: $(INFORM7WEB)/Tangled/inform7_ $(INFORM7WEB)/Internal/Miscellany/inform7_clib.c: $(INFORM7WEB)/Tangled/inform7_clib.c cp -f "$(INFORM7WEB)/Tangled/inform7_clib.c" "$(INFORM7WEB)/Internal/Miscellany/inform7_clib.c" +$(INFORM7WEB)/Internal/Miscellany/inform7_cslib.cs: $(INFORM7WEB)/Tangled/inform7_cslib.cs + cp -f "$(INFORM7WEB)/Tangled/inform7_cslib.cs" "$(INFORM7WEB)/Internal/Miscellany/inform7_cslib.cs" + # It is in fact sufficient to tangle just the inter subset of inform7 to make -# the two C-library files: +# the library files: $(INFORM7WEB)/Tangled/inform7_clib.h: inter/final-module/Chapter\ 5/*.w $(INWEBX) $(INTERWEB) -tangle @@ -216,6 +221,9 @@ $(INFORM7WEB)/Tangled/inform7_clib.h: inter/final-module/Chapter\ 5/*.w $(INFORM7WEB)/Tangled/inform7_clib.c: inter/final-module/Chapter\ 5/*.w $(INWEBX) $(INTERWEB) -tangle +$(INFORM7WEB)/Tangled/inform7_cslib.cs: inter/final-module/Chapter\ 6/*.w + $(INWEBX) $(INTERWEB) -tangle + # ----------------------------------------------------------------------------- # Target "makers" # ----------------------------------------------------------------------------- diff --git a/services/arch-module/Chapter 2/Target Virtual Machines.w b/services/arch-module/Chapter 2/Target Virtual Machines.w index 58318b7c7d..7cf0c4f0fb 100644 --- a/services/arch-module/Chapter 2/Target Virtual Machines.w +++ b/services/arch-module/Chapter 2/Target Virtual Machines.w @@ -55,6 +55,12 @@ void TargetVMs::create(void) { TargetVMs::new(Architectures::from_codename(I"32d"), I"C", VersionNumbers::from_text(I"1"), I"c", I"", I"", I"", NULL); + /* C# support added March 2023 */ + TargetVMs::new(Architectures::from_codename(I"32"), I"C#", + VersionNumbers::from_text(I"1"), I"cs", I"", I"", I"", NULL); + TargetVMs::new(Architectures::from_codename(I"32d"), I"C#", + VersionNumbers::from_text(I"1"), I"cs", I"", I"", I"", NULL); + /* Inventory support added March 2022 */ TargetVMs::new(Architectures::from_codename(I"16"), I"Inventory", VersionNumbers::from_text(I"1"), I"c", I"", I"", I"", NULL); From 4f5c117603291b28443366ec4b4bf37b8bbec13b Mon Sep 17 00:00:00 2001 From: Nathan Summers Date: Thu, 20 Jun 2024 14:14:17 -0400 Subject: [PATCH 2/2] Removed duplicate i7_texts --- inform7/Internal/Miscellany/inform7_cslib.cs | 4 ++-- inter/final-module/Chapter 6/C# Literals.w | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/inform7/Internal/Miscellany/inform7_cslib.cs b/inform7/Internal/Miscellany/inform7_cslib.cs index 39740ac714..5ca3e6a1b5 100644 --- a/inform7/Internal/Miscellany/inform7_cslib.cs +++ b/inform7/Internal/Miscellany/inform7_cslib.cs @@ -853,12 +853,12 @@ internal int i7_opcode_jisnan(int x) { } partial class Story { internal int i7_strings_base; + internal string[] i7_texts; } partial class Process { - string[] i7_texts; public string i7_text_to_CLR_string(int str) { - return i7_texts[str - story.i7_strings_base]; + return story.i7_texts[str - story.i7_strings_base]; } } partial class Process { diff --git a/inter/final-module/Chapter 6/C# Literals.w b/inter/final-module/Chapter 6/C# Literals.w index d2366ade2f..11b182b900 100644 --- a/inter/final-module/Chapter 6/C# Literals.w +++ b/inter/final-module/Chapter 6/C# Literals.w @@ -95,12 +95,12 @@ and the range they spread out over can be very large.) = (text to inform7_cslib.cs) partial class Story { internal int i7_strings_base; + internal string[] i7_texts; } partial class Process { - string[] i7_texts; public string i7_text_to_CLR_string(int str) { - return i7_texts[str - story.i7_strings_base]; + return story.i7_texts[str - story.i7_strings_base]; } } = @@ -199,10 +199,13 @@ void CSLiteralsModel::end_text(code_generation *gen) { segmentation_pos saved = CodeGen::select(gen, cs_quoted_text_I7CGS); text_stream *OUT = CodeGen::current(gen); WRITE("const int i7_mgl_Grammar__Version = 2;\n"); - WRITE("string[] i7_texts = {\n"); + CodeGen::deselect(gen, saved); + saved = CodeGen::select(gen, cs_constructor_I7CGS); + OUT = CodeGen::current(gen); + WRITE("i7_texts = new[] {\n"); text_stream *T; LOOP_OVER_LINKED_LIST(T, text_stream, CS_GEN_DATA(litdata.texts)) - WRITE("%S, ", T); + WRITE("%S, ", T); WRITE("\"\" };\n"); CodeGen::deselect(gen, saved); }