{"id":11114,"date":"2025-07-14T01:02:00","date_gmt":"2025-07-14T05:02:00","guid":{"rendered":"https:\/\/www.both.org\/?p=11114"},"modified":"2025-07-14T06:55:53","modified_gmt":"2025-07-14T10:55:53","slug":"a-small-algol-68-project-part-2","status":"publish","type":"post","link":"https:\/\/www.both.org\/?p=11114","title":{"rendered":"A Small Algol 68 Project, Part 2"},"content":{"rendered":"<div class=\"pld-like-dislike-wrap pld-template-1\">\r\n    <div class=\"pld-like-wrap  pld-common-wrap\">\r\n    <a href=\"javascript:void(0)\" class=\"pld-like-trigger pld-like-dislike-trigger  \" title=\"\" data-post-id=\"11114\" data-trigger-type=\"like\" data-restriction=\"cookie\" data-already-liked=\"0\">\r\n                        <i class=\"fas fa-thumbs-up\"><\/i>\r\n                <\/a>\r\n    <span class=\"pld-like-count-wrap pld-count-wrap\">    <\/span>\r\n<\/div><\/div>\n<p class=\"has-text-align-center\"><em>In memory of J. Kevin Douglas, a good friend and fellow fan of Algol 68<\/em><\/p>\n\n\n\n<p>In the <a href=\"https:\/\/www.both.org\/?p=11049\">last article in this series<\/a>, we described a simple linear least squares problem &#8211; fitting a line to a sequence of (x,y) coordinates &#8211; and we wrote a small Algol 68 program to create a test data set for use in the development of a program to carry out this fitting operation.<\/p>\n\n\n\n<p>In this article, we will develop a small collection of useful routines for reading text files and extracting data from them, setting us up for a more streamlined solution to the least squares problem.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Files in Algol 68<\/strong><\/h2>\n\n\n\n<p>I would venture to guess that most of us think of files, especially text files, as collections of characters with intervening line breaks.&nbsp; But back in the days when Algol 68 was being designed and <s>dinosaurs<\/s> mainframes ruled the world, there was a tendency to think of files in terms of things like record length and access methods.&nbsp; In the IBM mainframe world, access methods referred to whether files were suitable for sequential or random record access, whether they could be lengthened or shortened and so on.&nbsp; Many applications, including compilers for programming languages like FORTRAN, COBOL or PL\/1 were originally designed to interpret input coming from <a href=\"https:\/\/en.wikipedia.org\/wiki\/Keypunch\">80 column keypunch cards<\/a>.<\/p>\n\n\n\n<p>Moreover, different mainframes had differing word lengths; for example, the <a href=\"https:\/\/en.wikipedia.org\/wiki\/UNIVAC_1100\/2200_series\">Univac 1100 series <\/a>used a 36 bit word, whereas the IBM 360 and successors used a 32 bit word.&nbsp; Those 36 bit words in the Univac mainframes held <a href=\"https:\/\/www.fourmilab.ch\/documents\/univac\/fieldata.html\">six 6-bit characters<\/a>, encoded according to the US Army FIELDATA communications standard (upper case letters, numbers and a few symbols).&nbsp; The IBM 360\u2019s 32 bit words held <a href=\"https:\/\/en.wikipedia.org\/wiki\/EBCDIC\">four 8-bit characters<\/a> encoded as EBCDIC.<\/p>\n\n\n\n<p>Contrast this to our current perspective of 32 and 64 bit words and <a href=\"https:\/\/home.unicode.org\">Unicode character encoding<\/a>.<\/p>\n\n\n\n<p>Given that Algol 68 was designed according to the file gestalt of the 1960s, I find it kind of surprising that it seems to work quite well with the \u201cfile as stream of characters\u201d mindset that really kind of took flight thanks to the approach to files taken by the early Unix developers.<\/p>\n\n\n\n<p>So let\u2019s dig in.<\/p>\n\n\n\n<p>Like many modern programming languages, Algol 68 relies on opening files in order to acquire the ability to read or write them and closing them in order to relinquish this ability.<\/p>\n\n\n\n<p>The act of opening a file is handled by the open procedure defined as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>PROC open = (REF FILE f, STRING idf, CHANNEL chan) INT<\/code><\/code><\/pre>\n\n\n\n<p>The first parameter is a reference to an Algol 68 file data structure &#8211; the <code>FILE<\/code> &#8211; which is set by the procedure and returned to the caller.<\/p>\n\n\n\n<p>The second parameter is the constant externally-known name of the file; its identifier, <code>STRING idf<\/code>.<\/p>\n\n\n\n<p>The third parameter is the channel &#8211; <code>CHANNEL<\/code> &#8211; to be used for reading from or writing to the file.&nbsp; Back in the 60s, channels were the means to associate the program endpoint (the <code>FILE<\/code>) with the external storage referred to by the file name.<\/p>\n\n\n\n<p>A call to <code>OPEN()<\/code> returns an integer value which is the return code &#8211; the result &#8211; of attempting to open the file.<\/p>\n\n\n\n<p>Algol 68 Genie pre-defines a standard set of <code>FILE<\/code>s (<code>stand in, stand out, stand error <\/code>and<code> stand back<\/code>) and a standard set of <code>CHANNEL<\/code>s (<code>stand in channel, stand out channel, stand error channel, stand back channel, stand draw channel<\/code>).<sup data-fn=\"0feca6a4-0211-4a75-8d4c-03dc6522f325\" class=\"fn\"><a href=\"#0feca6a4-0211-4a75-8d4c-03dc6522f325\" id=\"0feca6a4-0211-4a75-8d4c-03dc6522f325-link\">1<\/a><\/sup>&nbsp; As in most programming languages used in Linux, we can associate those with physical files on the terminal command line.<\/p>\n\n\n\n<p>We can also define our own <code>FILE<\/code> and associate it with one of the existing channels (the purpose of the <code>open()<\/code> procedure).&nbsp; And of course there are other operations we can undertake; but for the purposes of this article, we\u2019re really only interested in reading the file, detecting the end of file while reading and closing it once we\u2019re done.<\/p>\n\n\n\n<p>The <code>get()<\/code> procedure is typically used to read lines from a file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>PROC get = (REF FILE f, &#91;] UNION (INTYPE, PROC (REF FILE) VOID) items) VOID<\/code><\/code><\/pre>\n\n\n\n<p>We see that <code>get()<\/code> is passed a reference to the <code>FILE<\/code>, allowing it to update the <code>FILE<\/code> object; and a complicated looking thing &#8211; an array of united modes, which can be either mode <code>INTYPE<\/code> or a <code>PROC<\/code>.&nbsp; I don\u2019t really want to make a huge digression at this point into united modes in general nor to <code>INTYPE<\/code>, which is itself a kind of united mode.&nbsp; Those impatient to find out more may wish to consult Chapter 7 of the Algol 68 Genie learning guide.<sup data-fn=\"053b0652-9946-42d4-8f99-7b2d03ed5ea6\" class=\"fn\"><a href=\"#053b0652-9946-42d4-8f99-7b2d03ed5ea6\" id=\"053b0652-9946-42d4-8f99-7b2d03ed5ea6-link\">2<\/a><\/sup>&nbsp; Suffice to say for now that <code>get()<\/code> can be made to return all manner of things if they are properly formatted; we will use it to return lines of the file, one by one.&nbsp;<\/p>\n\n\n\n<p>The <code>on logical file end()<\/code> procedure is used to register a procedure to be called when reading reaches the end of the file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>PROC on logical file end = (REF FILE f, PROC (REF FILE) BOOL p) VOID<\/code><\/code><\/pre>\n\n\n\n<p>This procedure is an interesting thing &#8211; it detects an <em>event<\/em> &#8211; in this case, reaching the logical end of the file &#8211; and calls another procedure to deal with that situation &#8211; what we might call a <em>listener<\/em> in today\u2019s jargon.<\/p>\n\n\n\n<p>The <code>close()<\/code> procedure is used to cleanly sever the connection between the external file and the program:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>PROC close = (REF FILE f) VOID<\/code><\/code><\/pre>\n\n\n\n<p>We can see that there is some boiler plate code involved here that we might not wish to deal with every time we want to read a text file, so let\u2019s encapsulate this code in our own procedure to simplify its use.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Streamlined text file reading in Algol 68<\/strong><\/h2>\n\n\n\n<p>Here is a procedure to streamline reading a text file line by line:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>1 PROC each line = (STRING input file name, PROC (STRING, INT) VOID l) VOID:\n2 BEGIN\n3 &nbsp; &nbsp; FILE input file;\n4 &nbsp; &nbsp; VOID(open(input file, input file name, standinchannel));\n5 &nbsp; &nbsp; BOOL fr := FALSE;\n6 &nbsp; &nbsp; on logical file end (input file, (REF FILE f) BOOL: fr := TRUE);\n7 &nbsp; &nbsp; INT linecount := 0;\n8 &nbsp; &nbsp; WHILE\n9 &nbsp; &nbsp; &nbsp; &nbsp; STRING line;\n10 &nbsp; &nbsp; &nbsp; &nbsp; get(input file,(line, newline));\n11 &nbsp; &nbsp; &nbsp; &nbsp; NOT fr\n12 &nbsp; &nbsp; DO\n13 &nbsp; &nbsp; &nbsp; &nbsp; linecount +:= 1;\n14 &nbsp; &nbsp; &nbsp; &nbsp; l(line, linecount)\n15 &nbsp; &nbsp; OD;\n16 &nbsp; &nbsp; close(input file)\n17 END # each line #;<\/code><\/pre>\n\n\n\n<p>Let\u2019s walk through this line by line:<\/p>\n\n\n\n<p>Line 1 declares the procedure, which takes a <code>STRING<\/code> argument (the external file name) and a <code>PROC<\/code> to be called to process each line once it\u2019s read, returning <code>VOID<\/code> (no value).&nbsp; Here you see Algol 68 giving us the ability to use what are called \u201chere procedures\u201d or \u201clambdas\u201d or \u201cclosures\u201d (with apologies for trampling on some fine distinctions here).&nbsp; We\u2019ll see how this is used below in a simple example.<\/p>\n\n\n\n<p>Line 2 declares the variable <code>input file<\/code>.<\/p>\n\n\n\n<p>Line 3 opens the <code>input file<\/code>&nbsp; using the <code>input file name<\/code> parameter and associates that with <code>standin channel<\/code>.&nbsp; The call is wrapped in a coercion of the return value to <code>VOID<\/code>. We\u2019ll assume for now that the user is careful to check that the external file exists and is readable, and doesn&#8217;t bother checking a return code.  Is this good design?  Well maybe not really; but I&#8217;m not an immediate fan of bubbling up the return code from <code>open()<\/code> to the caller of <code>each line()<\/code>, either.  I guess we could just halt at this point&#8230;<\/p>\n\n\n\n<p>Lines 4 and 5 declare a variable <code>fr<\/code> that is used to indicate whether or not the reading has reached the end of file; set <code>fr<\/code> to <code>FALSE<\/code> initially; and pass a listener procedure to <code>on logical file end<\/code> that sets <code>fr<\/code>&nbsp; to <code>TRUE<\/code> when the end of file is reached.<\/p>\n\n\n\n<p>Line 6 declares and initializes the <code>line count<\/code> variable, to be passed to the line listener routine.<\/p>\n\n\n\n<p>Lines 8 through 12 merit careful attention, as they offer a minor but clear demonstration of the concept of a serial clause yielding a value.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The construct <code>WHILE \u2026 DO \u2026 OD<\/code> executes whatever is between <code>DO<\/code> and <code>OD<\/code> repetitively as long as whatever is between <code>WHILE<\/code> and <code>DO<\/code> evaluates to <code>TRUE<\/code>.<\/li>\n\n\n\n<li>Line 9 declares the <code>STRING<\/code> variable <code>line<\/code>; line 10 calls the <code>get()<\/code> procedure to read <code>line<\/code> from <code>input file<\/code>.&nbsp; Note that <code>new line<\/code> is called to advance past the line end to the next line (or logical end of file).<\/li>\n\n\n\n<li>Line 10 yields a value that is either <code>TRUE<\/code> or <code>FALSE<\/code>, controlling whether the loop is executed again.&nbsp; Of course, this value is the negation of the value of the variable <code>fr<\/code> which is set to <code>TRUE<\/code> when logical end of file is reached.<\/li>\n<\/ul>\n\n\n\n<p>Lines 12 through 15 are now a bit of an anticlimax &#8211; in line 13 <code>line count<\/code> is incremented since we know line was successfully read by the serial clause between the <code>WHILE<\/code> and <code>DO<\/code>; and in line 14 we call the routine provided by the caller to manage each line, passing it the values in <code>line<\/code> and <code>line count<\/code>.<\/p>\n\n\n\n<p>Finally, in line 16 we close the file and we are done.<\/p>\n\n\n\n<p>A simple program that exercises this <code>each line()<\/code> procedure might look like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>each line(\u201c.profile\u201d, (STRING line, INT line count) VOID: BEGIN\n&nbsp;&nbsp;&nbsp;&nbsp;print((line count, \u201c:\u201d, line, new line))\nEND)<\/code><\/pre>\n\n\n\n<p>Once again, this demonstrates the use of an anonymous \u201chere procedure\u201d by the programmer, obscuring the boiler plate and concentrating on the details of what must be done, rather than how it\u2019s to be done.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Text files with delimited fields in Algol 68<\/strong><\/h2>\n\n\n\n<p>As we move into reading the data file needed for our least squares fit, we recognize that the problem looks a lot like something we\u2019ve dealt with in the <code>AWK<\/code> programming language in the past &#8211; a file whose lines are separated into fields with a delimiter character.<\/p>\n\n\n\n<p>Therefore, we need two things:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>A mechanism to split a string into an array of fields based on delimiter characters, and<\/li>\n\n\n\n<li>A slightly revised version of <code>each line()<\/code> that does that splitting<\/li>\n<\/ol>\n\n\n\n<p>Here is a procedure to split a string into an array of fields:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>1 PROC split = (STRING s, CHAR d) &#91;] STRING:\n2 BEGIN\n3 &nbsp; &nbsp; INT fieldcount := 1;\n4 &nbsp; &nbsp; FOR p FROM LWB s TO UPB s DO\n5 &nbsp; &nbsp; &nbsp; &nbsp; IF s&#91;p] = d THEN\n6 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fieldcount +:= 1\n7 &nbsp; &nbsp; &nbsp; &nbsp; FI\n8 &nbsp; &nbsp; OD;\n9 &nbsp; &nbsp; &#91;1:fieldcount] STRING fields;\n10 &nbsp; &nbsp; fieldcount := 1;\n11 &nbsp; &nbsp; INT fieldstart := LWB s;\n12 &nbsp; &nbsp; FOR p FROM LWB s TO UPB s DO\n13 &nbsp; &nbsp; &nbsp; &nbsp; IF s&#91;p] = d THEN\n14 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fields&#91;fieldcount] := (p &gt; fieldstart | s&#91;fieldstart:(p - 1)] | \"\");\n15 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fieldcount +:= 1;\n16 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fieldstart := p + 1\n17 &nbsp; &nbsp; &nbsp; &nbsp; FI\n18 &nbsp; &nbsp; OD;\n19 &nbsp; &nbsp; fields&#91;fieldcount] := (UPB s &gt; fieldstart | s&#91;fieldstart:UPB s] | \"\");\n20 &nbsp; &nbsp; fields\n21 END # split #;<\/code><\/pre>\n\n\n\n<p>I\u2019m just going to touch on the highlights in the above.<\/p>\n\n\n\n<p>Lines 3 through 8 count the number of delimiters and therefore the number of delimited fields.<\/p>\n\n\n\n<p>In line 4 we see <code>LWB s<\/code> and <code>UPB s<\/code> &#8211; the <code>LWB<\/code> operator gives the lower bound of the argument array; the <code>UPB<\/code> operator gives the upper bound of the argument array.&nbsp; Generally Algol 68 arrays start at 1 but let\u2019s get in a good habit here.<\/p>\n\n\n\n<p>Line 9 declares the array of fields.<\/p>\n\n\n\n<p>Lines 10 through 19 pass over the input string once more, copying the delimited text into the array of fields.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Line 14 has an interesting expression on the right hand side of the assignment statement.<\/li>\n\n\n\n<li>In line 14, we\u2019ve arrived at the situation where we have detected the next delimiter, which is at position <code>p<\/code> in the input string.<\/li>\n\n\n\n<li>If there is at least one character between the start of field and the next delimiter, then we return the <strong><em>slice<\/em><\/strong> of the input string starting at position <code>fieldstart<\/code> and going through to position <code>p - 1<\/code>.<\/li>\n\n\n\n<li>Otherwise the field is empty and we return the empty string <code>\u201c\u201d<\/code>.<\/li>\n\n\n\n<li>The construct <code>( \u2026 | \u2026 | \u2026 )<\/code> is equivalent to &#8211; shorthand for, really &#8211; <code>IF \u2026 THEN \u2026 ELSE \u2026 FI<\/code>, and returns either the last value of the <code>THEN<\/code> part or the last value of the <code>ELSE<\/code> part.&nbsp; So quite similar to the workings of the ternary operator in C or Java; except that it\u2019s the same old if-statement.<\/li>\n\n\n\n<li>Line 19 looks a lot like line 14 except we assume the end of the line is the final field delimiter.<\/li>\n<\/ul>\n\n\n\n<p>Next, we\u2019ll modify <code>each line()<\/code> to provide the split fields in a very <code>AWK<\/code>-ish fashion.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>1 PROC each delimited line = (STRING input file name, CHAR d, PROC (STRING, &#91;]STRING, INT) VOID l) VOID:\n2 BEGIN\n3 &nbsp; &nbsp; FILE input file;\n4 &nbsp; &nbsp; VOID(open(input file, input file name, standinchannel));\n5 &nbsp; &nbsp; BOOL fr := FALSE;\n6 &nbsp; &nbsp; on logical file end (input file, (REF FILE f) BOOL: fr := TRUE);\n7 &nbsp; &nbsp; INT linecount := 0;\n8 &nbsp; &nbsp; WHILE\n9 &nbsp; &nbsp; &nbsp; &nbsp; STRING line;\n10 &nbsp; &nbsp; &nbsp; &nbsp; get(input file,(line, newline));\n11 &nbsp; &nbsp; &nbsp; &nbsp; NOT fr\n12 &nbsp; &nbsp; DO\n13 &nbsp; &nbsp; &nbsp; &nbsp; linecount +:= 1;\n14 &nbsp; &nbsp; &nbsp; &nbsp; &#91;]STRING fields = split(line,d);\n15 &nbsp; &nbsp; &nbsp; &nbsp; l(line, fields, linecount)\n16 &nbsp; &nbsp; OD;\n17 &nbsp; &nbsp; close(input file)\n18 END # each delimited line #;<\/code><\/pre>\n\n\n\n<p>Really only three differences here:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>The procedure <code>each delimited line()<\/code> takes a third argument, <code>d<\/code>, which is the delimiter character;<\/li>\n\n\n\n<li>The procedure parameter takes a third argument &#8211; the array of strings containing the fields;<\/li>\n\n\n\n<li>The array <code>fields<\/code> is declared and set to the <code>split()<\/code> of the line based on the delimiter character.<\/li>\n<\/ol>\n\n\n\n<p>Here is the outline of our program to undertake the least squares fitting:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>BEGIN\n&nbsp;&nbsp;&nbsp;&nbsp;each delimited line(\"test_data.txt\", \",\", (STRING line, &#91;]STRING fields, INT line count) VOID: BEGIN\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print((line count, \" \", fields, new line))\n&nbsp;&nbsp;&nbsp;&nbsp;END)\nEND<\/code><\/pre>\n\n\n\n<p>In the next article, we\u2019ll add in the calculations in place of the <code>print()<\/code> call.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n<ol class=\"wp-block-footnotes\"><li id=\"0feca6a4-0211-4a75-8d4c-03dc6522f325\">\u00a0<a href=\"https:\/\/jmvdveer.home.xs4all.nl\/learning-algol-68-genie.pdf\"><em>Learning Algol 68 Genie<\/em><\/a>, 7.2 Channels and files, p. 120 <a href=\"#0feca6a4-0211-4a75-8d4c-03dc6522f325-link\" aria-label=\"Jump to footnote reference 1\">\u21a9\ufe0e<\/a><\/li><li id=\"053b0652-9946-42d4-8f99-7b2d03ed5ea6\">\u00a0Ibid, 7 Transput, p. 119. <a href=\"#053b0652-9946-42d4-8f99-7b2d03ed5ea6-link\" aria-label=\"Jump to footnote reference 2\">\u21a9\ufe0e<\/a><\/li><\/ol>\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In memory of J. Kevin Douglas, a good friend and fellow fan of Algol 68 In the last<\/p>\n","protected":false},"author":430,"featured_media":3096,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_lmt_disableupdate":"","_lmt_disable":"","footnotes":"[{\"content\":\"\u00a0<a href=\\\"https:\/\/jmvdveer.home.xs4all.nl\/learning-algol-68-genie.pdf\\\"><em>Learning Algol 68 Genie<\/em><\/a>, 7.2 Channels and files, p. 120\",\"id\":\"0feca6a4-0211-4a75-8d4c-03dc6522f325\"},{\"content\":\"\u00a0Ibid, 7 Transput, p. 119.\",\"id\":\"053b0652-9946-42d4-8f99-7b2d03ed5ea6\"}]"},"categories":[98,150],"tags":[783,152],"class_list":["post-11114","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-code","category-programming","tag-algol-68","tag-programming"],"modified_by":"David Both","_links":{"self":[{"href":"https:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/posts\/11114","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/users\/430"}],"replies":[{"embeddable":true,"href":"https:\/\/www.both.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=11114"}],"version-history":[{"count":15,"href":"https:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/posts\/11114\/revisions"}],"predecessor-version":[{"id":11200,"href":"https:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/posts\/11114\/revisions\/11200"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.both.org\/index.php?rest_route=\/wp\/v2\/media\/3096"}],"wp:attachment":[{"href":"https:\/\/www.both.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=11114"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.both.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=11114"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.both.org\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=11114"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}