mirror of
				https://github.com/SquidDev-CC/CC-Tweaked
				synced 2025-10-31 13:42:59 +00:00 
			
		
		
		
	Merge branch 'mc-1.16.x' into mc-1.18.x
This commit is contained in:
		| @@ -5,7 +5,7 @@ kotlin.stdlib.default.dependency=false | |||||||
| kotlin.jvm.target.validation.mode=error | kotlin.jvm.target.validation.mode=error | ||||||
|  |  | ||||||
| # Mod properties | # Mod properties | ||||||
| modVersion=1.101.2 | modVersion=1.101.3 | ||||||
|  |  | ||||||
| # Minecraft properties: We want to configure this here so we can read it in settings.gradle | # Minecraft properties: We want to configure this here so we can read it in settings.gradle | ||||||
| mcVersion=1.18.2 | mcVersion=1.18.2 | ||||||
|   | |||||||
| @@ -8,9 +8,13 @@ package dan200.computercraft.core.apis.http.options; | |||||||
| import com.google.common.net.InetAddresses; | import com.google.common.net.InetAddresses; | ||||||
| import dan200.computercraft.ComputerCraft; | import dan200.computercraft.ComputerCraft; | ||||||
| 
 | 
 | ||||||
|  | import java.net.Inet6Address; | ||||||
| import java.net.InetAddress; | import java.net.InetAddress; | ||||||
| import java.net.InetSocketAddress; | import java.net.InetSocketAddress; | ||||||
|  | import java.util.Arrays; | ||||||
|  | import java.util.Set; | ||||||
| import java.util.regex.Pattern; | import java.util.regex.Pattern; | ||||||
|  | import java.util.stream.Collectors; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * A predicate on an address. Matches against a domain and an ip address. |  * A predicate on an address. Matches against a domain and an ip address. | ||||||
| @@ -135,13 +139,36 @@ interface AddressPredicate | |||||||
|     { |     { | ||||||
|         static final PrivatePattern INSTANCE = new PrivatePattern(); |         static final PrivatePattern INSTANCE = new PrivatePattern(); | ||||||
| 
 | 
 | ||||||
|  |         private static final Set<InetAddress> additionalAddresses = Arrays.stream( new String[] { | ||||||
|  |             // Block various cloud providers internal IPs. | ||||||
|  |             "100.100.100.200", // Alibaba | ||||||
|  |             "192.0.0.192", // Oracle | ||||||
|  |         } ).map( InetAddresses::forString ).collect( Collectors.toSet() ); | ||||||
|  | 
 | ||||||
|         @Override |         @Override | ||||||
|         public boolean matches( InetAddress socketAddress ) |         public boolean matches( InetAddress socketAddress ) | ||||||
|         { |         { | ||||||
|             return socketAddress.isAnyLocalAddress() |             return socketAddress.isAnyLocalAddress()   // 0.0.0.0, ::0 | ||||||
|                 || socketAddress.isLoopbackAddress() |                 || socketAddress.isLoopbackAddress()   // 127.0.0.0/8, ::1 | ||||||
|                 || socketAddress.isLinkLocalAddress() |                 || socketAddress.isLinkLocalAddress()  // 169.254.0.0/16, fe80::/10 | ||||||
|                 || socketAddress.isSiteLocalAddress(); |                 || socketAddress.isSiteLocalAddress()  // 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fec0::/10 | ||||||
|  |                 || socketAddress.isMulticastAddress()  // 224.0.0.0/4, ff00::/8 | ||||||
|  |                 || isUniqueLocalAddress( socketAddress ) // fd00::/8 | ||||||
|  |                 || additionalAddresses.contains( socketAddress ); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Determine if an IP address lives inside the ULA address range. | ||||||
|  |          * | ||||||
|  |          * @param address The IP address to test. | ||||||
|  |          * @return Whether this address sits in the ULA address range. | ||||||
|  |          * @see <a href="https://en.wikipedia.org/wiki/Unique_local_address">Unique local address on Wikipedia</a> | ||||||
|  |          */ | ||||||
|  |         private boolean isUniqueLocalAddress( InetAddress address ) | ||||||
|  |         { | ||||||
|  |             // ULA is actually defined as fc00::/7 (so both fc00::/8 and fd00::/8). However, only the latter is actually | ||||||
|  |             // defined right now, so let's be conservative. | ||||||
|  |             return address instanceof Inet6Address && (address.getAddress()[0] & 0xff) == 0xfd; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -7,10 +7,13 @@ package dan200.computercraft.shared.command; | |||||||
| 
 | 
 | ||||||
| import com.mojang.brigadier.CommandDispatcher; | import com.mojang.brigadier.CommandDispatcher; | ||||||
| import com.mojang.brigadier.arguments.StringArgumentType; | import com.mojang.brigadier.arguments.StringArgumentType; | ||||||
|  | import com.mojang.brigadier.builder.RequiredArgumentBuilder; | ||||||
| import com.mojang.brigadier.exceptions.CommandSyntaxException; | import com.mojang.brigadier.exceptions.CommandSyntaxException; | ||||||
|  | import com.mojang.brigadier.suggestion.Suggestions; | ||||||
| import dan200.computercraft.api.peripheral.IPeripheral; | import dan200.computercraft.api.peripheral.IPeripheral; | ||||||
| import dan200.computercraft.core.computer.ComputerSide; | import dan200.computercraft.core.computer.ComputerSide; | ||||||
| import dan200.computercraft.core.metrics.Metrics; | import dan200.computercraft.core.metrics.Metrics; | ||||||
|  | import dan200.computercraft.shared.command.arguments.ComputersArgumentType; | ||||||
| import dan200.computercraft.shared.command.text.TableBuilder; | import dan200.computercraft.shared.command.text.TableBuilder; | ||||||
| import dan200.computercraft.shared.computer.core.ComputerFamily; | import dan200.computercraft.shared.computer.core.ComputerFamily; | ||||||
| import dan200.computercraft.shared.computer.core.ServerComputer; | import dan200.computercraft.shared.computer.core.ServerComputer; | ||||||
| @@ -200,7 +203,10 @@ public final class CommandComputerCraft | |||||||
| 
 | 
 | ||||||
|             .then( command( "queue" ) |             .then( command( "queue" ) | ||||||
|                 .requires( UserLevel.ANYONE ) |                 .requires( UserLevel.ANYONE ) | ||||||
|                 .arg( "computer", manyComputers() ) |                 .arg( | ||||||
|  |                     RequiredArgumentBuilder.<CommandSourceStack, ComputersArgumentType.ComputersSupplier>argument( "computer", manyComputers() ) | ||||||
|  |                         .suggests( ( context, builder ) -> Suggestions.empty() ) | ||||||
|  |                 ) | ||||||
|                 .argManyValue( "args", StringArgumentType.string(), Collections.emptyList() ) |                 .argManyValue( "args", StringArgumentType.string(), Collections.emptyList() ) | ||||||
|                 .executes( ( ctx, args ) -> { |                 .executes( ( ctx, args ) -> { | ||||||
|                     Collection<ServerComputer> computers = getComputersArgument( ctx, "computer" ); |                     Collection<ServerComputer> computers = getComputersArgument( ctx, "computer" ); | ||||||
|   | |||||||
| @@ -61,12 +61,36 @@ public enum UserLevel implements Predicate<CommandSourceStack> | |||||||
|         return source.hasPermission( toLevel() ); |         return source.hasPermission( toLevel() ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Take the union of two {@link UserLevel}s. | ||||||
|  |      * <p> | ||||||
|  |      * This satisfies the property that for all sources {@code s}, {@code a.test(s) || b.test(s) == (a ∪ b).test(s)}. | ||||||
|  |      * | ||||||
|  |      * @param left  The first user level to take the union of. | ||||||
|  |      * @param right The second user level to take the union of. | ||||||
|  |      * @return The union of two levels. | ||||||
|  |      */ | ||||||
|  |     public static UserLevel union( UserLevel left, UserLevel right ) | ||||||
|  |     { | ||||||
|  |         if( left == right ) return left; | ||||||
|  | 
 | ||||||
|  |         // x ∪ ANYONE = ANYONE | ||||||
|  |         if( left == ANYONE || right == ANYONE ) return ANYONE; | ||||||
|  | 
 | ||||||
|  |         // x ∪ OWNER = OWNER | ||||||
|  |         if( left == OWNER ) return right; | ||||||
|  |         if( right == OWNER ) return left; | ||||||
|  | 
 | ||||||
|  |         // At this point, we have x != y and x, y ∈ { OP, OWNER_OP }. | ||||||
|  |         return OWNER_OP; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private static boolean isOwner( CommandSourceStack source ) |     private static boolean isOwner( CommandSourceStack source ) | ||||||
|     { |     { | ||||||
|         MinecraftServer server = source.getServer(); |         MinecraftServer server = source.getServer(); | ||||||
|         Entity sender = source.getEntity(); |         Entity sender = source.getEntity(); | ||||||
|         return server.isDedicatedServer() |         return server.isDedicatedServer() | ||||||
|             ? source.getEntity() == null && source.hasPermission( 4 ) && source.getTextName().equals( "Server" ) |             ? source.getEntity() == null && source.hasPermission( 4 ) && source.getTextName().equals( "Server" ) | ||||||
|             : sender instanceof Player player && player.getGameProfile().getName().equalsIgnoreCase( server.getServerModName() ); |             : sender instanceof Player player && server.isSingleplayerOwner( player.getGameProfile() ); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -52,10 +52,15 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> | |||||||
|         return this; |         return this; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public CommandBuilder<S> arg( ArgumentBuilder<S, ?> arg ) | ||||||
|  |     { | ||||||
|  |         args.add( arg ); | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public CommandBuilder<S> arg( String name, ArgumentType<?> type ) |     public CommandBuilder<S> arg( String name, ArgumentType<?> type ) | ||||||
|     { |     { | ||||||
|         args.add( RequiredArgumentBuilder.argument( name, type ) ); |         return arg( RequiredArgumentBuilder.argument( name, type ) ); | ||||||
|         return this; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public <T> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argManyValue( String name, ArgumentType<T> type, List<T> empty ) |     public <T> CommandNodeBuilder<S, ArgCommand<S, List<T>>> argManyValue( String name, ArgumentType<T> type, List<T> empty ) | ||||||
| @@ -84,7 +89,7 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> | |||||||
| 
 | 
 | ||||||
|         return command -> { |         return command -> { | ||||||
|             // The node for no arguments |             // The node for no arguments | ||||||
|             ArgumentBuilder<S, ?> tail = tail( ctx -> command.run( ctx, empty.get() ) ); |             ArgumentBuilder<S, ?> tail = setupTail( ctx -> command.run( ctx, empty.get() ) ); | ||||||
| 
 | 
 | ||||||
|             // The node for one or more arguments |             // The node for one or more arguments | ||||||
|             ArgumentBuilder<S, ?> moreArg = RequiredArgumentBuilder |             ArgumentBuilder<S, ?> moreArg = RequiredArgumentBuilder | ||||||
| @@ -93,7 +98,7 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> | |||||||
| 
 | 
 | ||||||
|             // Chain all of them together! |             // Chain all of them together! | ||||||
|             tail.then( moreArg ); |             tail.then( moreArg ); | ||||||
|             return link( tail ); |             return buildTail( tail ); | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -106,22 +111,18 @@ public class CommandBuilder<S> implements CommandNodeBuilder<S, Command<S>> | |||||||
|     @Override |     @Override | ||||||
|     public CommandNode<S> executes( Command<S> command ) |     public CommandNode<S> executes( Command<S> command ) | ||||||
|     { |     { | ||||||
|         if( args.isEmpty() ) throw new IllegalStateException( "Cannot have empty arg chain builder" ); |         return buildTail( setupTail( command ) ); | ||||||
| 
 |  | ||||||
|         return link( tail( command ) ); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private ArgumentBuilder<S, ?> tail( Command<S> command ) |     private ArgumentBuilder<S, ?> setupTail( Command<S> command ) | ||||||
|     { |     { | ||||||
|         ArgumentBuilder<S, ?> defaultTail = args.get( args.size() - 1 ); |         return args.get( args.size() - 1 ).executes( command ); | ||||||
|         defaultTail.executes( command ); |  | ||||||
|         if( requires != null ) defaultTail.requires( requires ); |  | ||||||
|         return defaultTail; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private CommandNode<S> link( ArgumentBuilder<S, ?> tail ) |     private CommandNode<S> buildTail( ArgumentBuilder<S, ?> tail ) | ||||||
|     { |     { | ||||||
|         for( int i = args.size() - 2; i >= 0; i-- ) tail = args.get( i ).then( tail ); |         for( int i = args.size() - 2; i >= 0; i-- ) tail = args.get( i ).then( tail ); | ||||||
|  |         if( requires != null ) tail.requires( requires ); | ||||||
|         return tail.build(); |         return tail.build(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; | |||||||
| import com.mojang.brigadier.context.CommandContext; | import com.mojang.brigadier.context.CommandContext; | ||||||
| import com.mojang.brigadier.tree.CommandNode; | import com.mojang.brigadier.tree.CommandNode; | ||||||
| import com.mojang.brigadier.tree.LiteralCommandNode; | import com.mojang.brigadier.tree.LiteralCommandNode; | ||||||
|  | import dan200.computercraft.shared.command.UserLevel; | ||||||
| import net.minecraft.ChatFormatting; | import net.minecraft.ChatFormatting; | ||||||
| import net.minecraft.commands.CommandSourceStack; | import net.minecraft.commands.CommandSourceStack; | ||||||
| import net.minecraft.network.chat.ClickEvent; | import net.minecraft.network.chat.ClickEvent; | ||||||
| @@ -22,6 +23,10 @@ import net.minecraft.network.chat.TextComponent; | |||||||
| import javax.annotation.Nonnull; | import javax.annotation.Nonnull; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Collection; | import java.util.Collection; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.function.Predicate; | ||||||
|  | import java.util.stream.Collectors; | ||||||
|  | import java.util.stream.Stream; | ||||||
| 
 | 
 | ||||||
| import static dan200.computercraft.shared.command.text.ChatHelpers.coloured; | import static dan200.computercraft.shared.command.text.ChatHelpers.coloured; | ||||||
| import static dan200.computercraft.shared.command.text.ChatHelpers.translate; | import static dan200.computercraft.shared.command.text.ChatHelpers.translate; | ||||||
| @@ -44,6 +49,33 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<Command | |||||||
|         return new HelpingArgumentBuilder( literal ); |         return new HelpingArgumentBuilder( literal ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public LiteralArgumentBuilder<CommandSourceStack> requires( Predicate<CommandSourceStack> requirement ) | ||||||
|  |     { | ||||||
|  |         throw new IllegalStateException( "Cannot use requires on a HelpingArgumentBuilder" ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public Predicate<CommandSourceStack> getRequirement() | ||||||
|  |     { | ||||||
|  |         // The requirement of this node is the union of all child's requirements. | ||||||
|  |         List<Predicate<CommandSourceStack>> requirements = Stream.concat( | ||||||
|  |             children.stream().map( ArgumentBuilder::getRequirement ), | ||||||
|  |             getArguments().stream().map( CommandNode::getRequirement ) | ||||||
|  |         ).collect( Collectors.toList() ); | ||||||
|  | 
 | ||||||
|  |         // If all requirements are a UserLevel, take the union of those instead. | ||||||
|  |         UserLevel userLevel = UserLevel.OWNER; | ||||||
|  |         for( Predicate<CommandSourceStack> requirement : requirements ) | ||||||
|  |         { | ||||||
|  |             if( !(requirement instanceof UserLevel level) ) return x -> requirements.stream().anyMatch( y -> y.test( x ) ); | ||||||
|  |             userLevel = UserLevel.union( userLevel, level ); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return userLevel; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public LiteralArgumentBuilder<CommandSourceStack> executes( final Command<CommandSourceStack> command ) |     public LiteralArgumentBuilder<CommandSourceStack> executes( final Command<CommandSourceStack> command ) | ||||||
|     { |     { | ||||||
| @@ -99,9 +131,7 @@ public final class HelpingArgumentBuilder extends LiteralArgumentBuilder<Command | |||||||
|         helpCommand.node = node; |         helpCommand.node = node; | ||||||
| 
 | 
 | ||||||
|         // Set up a /... help command |         // Set up a /... help command | ||||||
|         LiteralArgumentBuilder<CommandSourceStack> helpNode = LiteralArgumentBuilder.<CommandSourceStack>literal( "help" ) |         LiteralArgumentBuilder<CommandSourceStack> helpNode = LiteralArgumentBuilder.<CommandSourceStack>literal( "help" ).executes( helpCommand ); | ||||||
|             .requires( x -> getArguments().stream().anyMatch( y -> y.getRequirement().test( x ) ) ) |  | ||||||
|             .executes( helpCommand ); |  | ||||||
| 
 | 
 | ||||||
|         // Add all normal command children to this and the help node |         // Add all normal command children to this and the help node | ||||||
|         for( CommandNode<CommandSourceStack> child : getArguments() ) |         for( CommandNode<CommandSourceStack> child : getArguments() ) | ||||||
|   | |||||||
| @@ -167,7 +167,7 @@ public class BlockTurtle extends BlockComputerBase<TileTurtle> implements Simple | |||||||
|     @Override |     @Override | ||||||
|     public float getExplosionResistance( BlockState state, BlockGetter world, BlockPos pos, Explosion explosion ) |     public float getExplosionResistance( BlockState state, BlockGetter world, BlockPos pos, Explosion explosion ) | ||||||
|     { |     { | ||||||
|         Entity exploder = explosion.getExploder(); |         Entity exploder = explosion == null ? null : explosion.getExploder(); | ||||||
|         if( getFamily() == ComputerFamily.ADVANCED || exploder instanceof LivingEntity || exploder instanceof AbstractHurtingProjectile ) |         if( getFamily() == ComputerFamily.ADVANCED || exploder instanceof LivingEntity || exploder instanceof AbstractHurtingProjectile ) | ||||||
|         { |         { | ||||||
|             return 2000; |             return 2000; | ||||||
|   | |||||||
| @@ -82,7 +82,7 @@ public class TurtleModem extends AbstractTurtleUpgrade | |||||||
|         } |         } | ||||||
|         else |         else | ||||||
|         { |         { | ||||||
|             leftOffModel = new ResourceLocation( ComputerCraft.MOD_ID, "turtle_modem_normal_off_left" ); |             leftOffModel = new ResourceLocation( ComputerCraft.MOD_ID, "block/turtle_modem_normal_off_left" ); | ||||||
|             rightOffModel = new ResourceLocation( ComputerCraft.MOD_ID, "block/turtle_modem_normal_off_right" ); |             rightOffModel = new ResourceLocation( ComputerCraft.MOD_ID, "block/turtle_modem_normal_off_right" ); | ||||||
|             leftOnModel = new ResourceLocation( ComputerCraft.MOD_ID, "block/turtle_modem_normal_on_left" ); |             leftOnModel = new ResourceLocation( ComputerCraft.MOD_ID, "block/turtle_modem_normal_on_left" ); | ||||||
|             rightOnModel = new ResourceLocation( ComputerCraft.MOD_ID, "block/turtle_modem_normal_on_right" ); |             rightOnModel = new ResourceLocation( ComputerCraft.MOD_ID, "block/turtle_modem_normal_on_right" ); | ||||||
|   | |||||||
| @@ -153,12 +153,22 @@ function locate(_nTimeout, _bDebug) | |||||||
|                     if tFix.nDistance == 0 then |                     if tFix.nDistance == 0 then | ||||||
|                         pos1, pos2 = tFix.vPosition, nil |                         pos1, pos2 = tFix.vPosition, nil | ||||||
|                     else |                     else | ||||||
|                         table.insert(tFixes, tFix) |                         -- Insert our new position in our table, with a maximum of three items. If this is close to a | ||||||
|  |                         -- previous position, replace that instead of inserting. | ||||||
|  |                         local insIndex = math.min(3, #tFixes + 1) | ||||||
|  |                         for i, older in pairs(tFixes) do | ||||||
|  |                             if (older.vPosition - tFix.vPosition):length() < 1 then | ||||||
|  |                                 insIndex = i | ||||||
|  |                                 break | ||||||
|  |                             end | ||||||
|  |                         end | ||||||
|  |                         tFixes[insIndex] = tFix | ||||||
|  |  | ||||||
|                         if #tFixes >= 3 then |                         if #tFixes >= 3 then | ||||||
|                             if not pos1 then |                             if not pos1 then | ||||||
|                                 pos1, pos2 = trilaterate(tFixes[1], tFixes[2], tFixes[#tFixes]) |                                 pos1, pos2 = trilaterate(tFixes[1], tFixes[2], tFixes[3]) | ||||||
|                             else |                             else | ||||||
|                                 pos1, pos2 = narrow(pos1, pos2, tFixes[#tFixes]) |                                 pos1, pos2 = narrow(pos1, pos2, tFixes[3]) | ||||||
|                             end |                             end | ||||||
|                         end |                         end | ||||||
|                     end |                     end | ||||||
|   | |||||||
| @@ -74,8 +74,7 @@ function undefine(name) | |||||||
|     details[name] = nil |     details[name] = nil | ||||||
| end | end | ||||||
|  |  | ||||||
| local function set_value(name, value) | local function set_value(name, new) | ||||||
|     local new = reserialize(value) |  | ||||||
|     local old = values[name] |     local old = values[name] | ||||||
|     if old == nil then |     if old == nil then | ||||||
|         local opt = details[name] |         local opt = details[name] | ||||||
| @@ -103,7 +102,7 @@ function set(name, value) | |||||||
|     local opt = details[name] |     local opt = details[name] | ||||||
|     if opt and opt.type then expect(2, value, opt.type) end |     if opt and opt.type then expect(2, value, opt.type) end | ||||||
|  |  | ||||||
|     set_value(name, value) |     set_value(name, reserialize(value)) | ||||||
| end | end | ||||||
|  |  | ||||||
| --- Get the value of a setting. | --- Get the value of a setting. | ||||||
| @@ -214,7 +213,9 @@ function load(sPath) | |||||||
|         if type(k) == "string" and (ty_v == "string" or ty_v == "number" or ty_v == "boolean" or ty_v == "table") then |         if type(k) == "string" and (ty_v == "string" or ty_v == "number" or ty_v == "boolean" or ty_v == "table") then | ||||||
|             local opt = details[k] |             local opt = details[k] | ||||||
|             if not opt or not opt.type or ty_v == opt.type then |             if not opt or not opt.type or ty_v == opt.type then | ||||||
|                 set_value(k, v) |                 -- This may fail if the table is recursive (or otherwise cannot be serialized). | ||||||
|  |                 local ok, v = pcall(reserialize, v) | ||||||
|  |                 if ok then set_value(k, v) end | ||||||
|             end |             end | ||||||
|         end |         end | ||||||
|     end |     end | ||||||
|   | |||||||
| @@ -292,6 +292,13 @@ local g_tLuaKeywords = { | |||||||
|     ["while"] = true, |     ["while"] = true, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | --- A version of the ipairs iterator which ignores metamethods | ||||||
|  | local function inext(tbl, i) | ||||||
|  |     i = (i or 0) + 1 | ||||||
|  |     local v = rawget(tbl, i) | ||||||
|  |     if v == nil then return nil else return i, v end | ||||||
|  | end | ||||||
|  |  | ||||||
| local serialize_infinity = math.huge | local serialize_infinity = math.huge | ||||||
| local function serialize_impl(t, tracking, indent, opts) | local function serialize_impl(t, tracking, indent, opts) | ||||||
|     local sType = type(t) |     local sType = type(t) | ||||||
| @@ -318,11 +325,11 @@ local function serialize_impl(t, tracking, indent, opts) | |||||||
|  |  | ||||||
|             result = open |             result = open | ||||||
|             local seen_keys = {} |             local seen_keys = {} | ||||||
|             for k, v in ipairs(t) do |             for k, v in inext, t do | ||||||
|                 seen_keys[k] = true |                 seen_keys[k] = true | ||||||
|                 result = result .. sub_indent .. serialize_impl(v, tracking, sub_indent, opts) .. comma |                 result = result .. sub_indent .. serialize_impl(v, tracking, sub_indent, opts) .. comma | ||||||
|             end |             end | ||||||
|             for k, v in pairs(t) do |             for k, v in next, t do | ||||||
|                 if not seen_keys[k] then |                 if not seen_keys[k] then | ||||||
|                     local sEntry |                     local sEntry | ||||||
|                     if type(k) == "string" and not g_tLuaKeywords[k] and string.match(k, "^[%a_][%a%d_]*$") then |                     if type(k) == "string" and not g_tLuaKeywords[k] and string.match(k, "^[%a_][%a%d_]*$") then | ||||||
|   | |||||||
| @@ -127,11 +127,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible) | |||||||
|         local sEmptyTextColor = tEmptyColorLines[nTextColor] |         local sEmptyTextColor = tEmptyColorLines[nTextColor] | ||||||
|         local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor] |         local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor] | ||||||
|         for y = 1, nHeight do |         for y = 1, nHeight do | ||||||
|             tLines[y] = { |             tLines[y] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor } | ||||||
|                 text = sEmptyText, |  | ||||||
|                 textColor = sEmptyTextColor, |  | ||||||
|                 backgroundColor = sEmptyBackgroundColor, |  | ||||||
|             } |  | ||||||
|         end |         end | ||||||
|  |  | ||||||
|         for i = 0, 15 do |         for i = 0, 15 do | ||||||
| @@ -161,7 +157,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible) | |||||||
|     local function redrawLine(n) |     local function redrawLine(n) | ||||||
|         local tLine = tLines[n] |         local tLine = tLines[n] | ||||||
|         parent.setCursorPos(nX, nY + n - 1) |         parent.setCursorPos(nX, nY + n - 1) | ||||||
|         parent.blit(tLine.text, tLine.textColor, tLine.backgroundColor) |         parent.blit(tLine[1], tLine[2], tLine[3]) | ||||||
|     end |     end | ||||||
|  |  | ||||||
|     local function redraw() |     local function redraw() | ||||||
| @@ -184,9 +180,9 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible) | |||||||
|                 -- Modify line |                 -- Modify line | ||||||
|                 local tLine = tLines[nCursorY] |                 local tLine = tLines[nCursorY] | ||||||
|                 if nStart == 1 and nEnd == nWidth then |                 if nStart == 1 and nEnd == nWidth then | ||||||
|                     tLine.text = sText |                     tLine[1] = sText | ||||||
|                     tLine.textColor = sTextColor |                     tLine[2] = sTextColor | ||||||
|                     tLine.backgroundColor = sBackgroundColor |                     tLine[3] = sBackgroundColor | ||||||
|                 else |                 else | ||||||
|                     local sClippedText, sClippedTextColor, sClippedBackgroundColor |                     local sClippedText, sClippedTextColor, sClippedBackgroundColor | ||||||
|                     if nStart < 1 then |                     if nStart < 1 then | ||||||
| @@ -206,9 +202,9 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible) | |||||||
|                         sClippedBackgroundColor = sBackgroundColor |                         sClippedBackgroundColor = sBackgroundColor | ||||||
|                     end |                     end | ||||||
|  |  | ||||||
|                     local sOldText = tLine.text |                     local sOldText = tLine[1] | ||||||
|                     local sOldTextColor = tLine.textColor |                     local sOldTextColor = tLine[2] | ||||||
|                     local sOldBackgroundColor = tLine.backgroundColor |                     local sOldBackgroundColor = tLine[3] | ||||||
|                     local sNewText, sNewTextColor, sNewBackgroundColor |                     local sNewText, sNewTextColor, sNewBackgroundColor | ||||||
|                     if nStart > 1 then |                     if nStart > 1 then | ||||||
|                         local nOldEnd = nStart - 1 |                         local nOldEnd = nStart - 1 | ||||||
| @@ -227,9 +223,9 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible) | |||||||
|                         sNewBackgroundColor = sNewBackgroundColor .. string_sub(sOldBackgroundColor, nOldStart, nWidth) |                         sNewBackgroundColor = sNewBackgroundColor .. string_sub(sOldBackgroundColor, nOldStart, nWidth) | ||||||
|                     end |                     end | ||||||
|  |  | ||||||
|                     tLine.text = sNewText |                     tLine[1] = sNewText | ||||||
|                     tLine.textColor = sNewTextColor |                     tLine[2] = sNewTextColor | ||||||
|                     tLine.backgroundColor = sNewBackgroundColor |                     tLine[3] = sNewBackgroundColor | ||||||
|                 end |                 end | ||||||
|  |  | ||||||
|                 -- Redraw line |                 -- Redraw line | ||||||
| @@ -276,11 +272,10 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible) | |||||||
|         local sEmptyTextColor = tEmptyColorLines[nTextColor] |         local sEmptyTextColor = tEmptyColorLines[nTextColor] | ||||||
|         local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor] |         local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor] | ||||||
|         for y = 1, nHeight do |         for y = 1, nHeight do | ||||||
|             tLines[y] = { |             local line = tLines[y] | ||||||
|                 text = sEmptyText, |             line[1] = sEmptyText | ||||||
|                 textColor = sEmptyTextColor, |             line[2] = sEmptyTextColor | ||||||
|                 backgroundColor = sEmptyBackgroundColor, |             line[3] = sEmptyBackgroundColor | ||||||
|             } |  | ||||||
|         end |         end | ||||||
|         if bVisible then |         if bVisible then | ||||||
|             redraw() |             redraw() | ||||||
| @@ -291,14 +286,10 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible) | |||||||
|  |  | ||||||
|     function window.clearLine() |     function window.clearLine() | ||||||
|         if nCursorY >= 1 and nCursorY <= nHeight then |         if nCursorY >= 1 and nCursorY <= nHeight then | ||||||
|             local sEmptyText = sEmptySpaceLine |             local line = tLines[nCursorY] | ||||||
|             local sEmptyTextColor = tEmptyColorLines[nTextColor] |             line[1] = sEmptySpaceLine | ||||||
|             local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor] |             line[2] = tEmptyColorLines[nTextColor] | ||||||
|             tLines[nCursorY] = { |             line[3] = tEmptyColorLines[nBackgroundColor] | ||||||
|                 text = sEmptyText, |  | ||||||
|                 textColor = sEmptyTextColor, |  | ||||||
|                 backgroundColor = sEmptyBackgroundColor, |  | ||||||
|             } |  | ||||||
|             if bVisible then |             if bVisible then | ||||||
|                 redrawLine(nCursorY) |                 redrawLine(nCursorY) | ||||||
|                 updateCursorColor() |                 updateCursorColor() | ||||||
| @@ -427,11 +418,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible) | |||||||
|                 if y >= 1 and y <= nHeight then |                 if y >= 1 and y <= nHeight then | ||||||
|                     tNewLines[newY] = tLines[y] |                     tNewLines[newY] = tLines[y] | ||||||
|                 else |                 else | ||||||
|                     tNewLines[newY] = { |                     tNewLines[newY] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor } | ||||||
|                         text = sEmptyText, |  | ||||||
|                         textColor = sEmptyTextColor, |  | ||||||
|                         backgroundColor = sEmptyBackgroundColor, |  | ||||||
|                     } |  | ||||||
|                 end |                 end | ||||||
|             end |             end | ||||||
|             tLines = tNewLines |             tLines = tNewLines | ||||||
| @@ -474,7 +461,8 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible) | |||||||
|             error("Line is out of range.", 2) |             error("Line is out of range.", 2) | ||||||
|         end |         end | ||||||
|  |  | ||||||
|         return tLines[y].text, tLines[y].textColor, tLines[y].backgroundColor |         local line = tLines[y] | ||||||
|  |         return line[1], line[2], line[3] | ||||||
|     end |     end | ||||||
|  |  | ||||||
|     -- Other functions |     -- Other functions | ||||||
| @@ -570,26 +558,22 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible) | |||||||
|             local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor] |             local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor] | ||||||
|             for y = 1, new_height do |             for y = 1, new_height do | ||||||
|                 if y > nHeight then |                 if y > nHeight then | ||||||
|                     tNewLines[y] = { |                     tNewLines[y] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor } | ||||||
|                         text = sEmptyText, |  | ||||||
|                         textColor = sEmptyTextColor, |  | ||||||
|                         backgroundColor = sEmptyBackgroundColor, |  | ||||||
|                     } |  | ||||||
|                 else |                 else | ||||||
|                     local tOldLine = tLines[y] |                     local tOldLine = tLines[y] | ||||||
|                     if new_width == nWidth then |                     if new_width == nWidth then | ||||||
|                         tNewLines[y] = tOldLine |                         tNewLines[y] = tOldLine | ||||||
|                     elseif new_width < nWidth then |                     elseif new_width < nWidth then | ||||||
|                         tNewLines[y] = { |                         tNewLines[y] = { | ||||||
|                             text = string_sub(tOldLine.text, 1, new_width), |                             string_sub(tOldLine[1], 1, new_width), | ||||||
|                             textColor = string_sub(tOldLine.textColor, 1, new_width), |                             string_sub(tOldLine[2], 1, new_width), | ||||||
|                             backgroundColor = string_sub(tOldLine.backgroundColor, 1, new_width), |                             string_sub(tOldLine[3], 1, new_width), | ||||||
|                         } |                         } | ||||||
|                     else |                     else | ||||||
|                         tNewLines[y] = { |                         tNewLines[y] = { | ||||||
|                             text = tOldLine.text .. string_sub(sEmptyText, nWidth + 1, new_width), |                             tOldLine[1] .. string_sub(sEmptyText, nWidth + 1, new_width), | ||||||
|                             textColor = tOldLine.textColor .. string_sub(sEmptyTextColor, nWidth + 1, new_width), |                             tOldLine[2] .. string_sub(sEmptyTextColor, nWidth + 1, new_width), | ||||||
|                             backgroundColor = tOldLine.backgroundColor .. string_sub(sEmptyBackgroundColor, nWidth + 1, new_width), |                             tOldLine[3] .. string_sub(sEmptyBackgroundColor, nWidth + 1, new_width), | ||||||
|                         } |                         } | ||||||
|                     end |                     end | ||||||
|                 end |                 end | ||||||
|   | |||||||
| @@ -1,3 +1,21 @@ | |||||||
|  | # New features in CC: Tweaked 1.101.3 | ||||||
|  | 
 | ||||||
|  | * Improve syntax errors when missing commas in tables, and on trailing commas in parameter lists. | ||||||
|  | * `speaker` program now reports an error on common unsupported audio formats. | ||||||
|  | * Small optimisations to the `window` API. | ||||||
|  | 
 | ||||||
|  | Several bug fixes: | ||||||
|  | * Fix the REPL syntax reporting crashing on valid parses. | ||||||
|  | * Ignore metatables in `textutils.serialize`. | ||||||
|  | * Fix `gps.locate` returning `nan` when receiving a duplicate location (Wojbie). | ||||||
|  | * Ignore metatables in `textutils.serialize`. | ||||||
|  | * Fix wireless turtles having an invalid model. | ||||||
|  | * Fix crash when turtles are exploded by a null explosion. | ||||||
|  | * Lua REPL no longer accepts `)(` as a valid expression. | ||||||
|  | * Fix several inconsistencies with `require`/`package.path` in the Lua REPL (Wojbie). | ||||||
|  | * Fix private several IP address ranges not being blocked by the `$private` rule. | ||||||
|  | * Improve permission checks in the `/computercraft` command. | ||||||
|  | 
 | ||||||
| # New features in CC: Tweaked 1.101.2 | # New features in CC: Tweaked 1.101.2 | ||||||
| 
 | 
 | ||||||
| * Error messages in `edit` are now displayed in red on advanced computers. | * Error messages in `edit` are now displayed in red on advanced computers. | ||||||
|   | |||||||
| @@ -1,16 +1,19 @@ | |||||||
| New features in CC: Tweaked 1.101.2 | New features in CC: Tweaked 1.101.3 | ||||||
| 
 | 
 | ||||||
| * Error messages in `edit` are now displayed in red on advanced computers. | * Improve syntax errors when missing commas in tables, and on trailing commas in parameter lists. | ||||||
| * Improvements to the display of errors in the shell and REPL. | * `speaker` program now reports an error on common unsupported audio formats. | ||||||
|  | * Small optimisations to the `window` API. | ||||||
| 
 | 
 | ||||||
| Several bug fixes: | Several bug fixes: | ||||||
| * Fix `import.lua` failing to upload a file. | * Fix the REPL syntax reporting crashing on valid parses. | ||||||
| * Fix several issues with sparse Lua tables (Shiranuit). | * Ignore metatables in `textutils.serialize`. | ||||||
| * Computer upgrades now accept normal computers, rather than uselessly allowing you to upgrade an advanced computer to an advanced computer! | * Fix `gps.locate` returning `nan` when receiving a duplicate location (Wojbie). | ||||||
| * Correctly clamp speaker volume. | * Ignore metatables in `textutils.serialize`. | ||||||
| * Fix rednet queueing the wrong message when sending a message to the current computer. | * Fix wireless turtles having an invalid model. | ||||||
| * Fix the Lua VM crashing when a `__len` metamethod yields. | * Fix crash when turtles are exploded by a null explosion. | ||||||
| * Trim spaces from filesystem paths. | * Lua REPL no longer accepts `)(` as a valid expression. | ||||||
| * Correctly format 12AM/PM with `%I`. | * Fix several inconsistencies with `require`/`package.path` in the Lua REPL (Wojbie). | ||||||
|  | * Fix private several IP address ranges not being blocked by the `$private` rule. | ||||||
|  | * Improve permission checks in the `/computercraft` command. | ||||||
| 
 | 
 | ||||||
| Type "help changelog" to see the full version history. | Type "help changelog" to see the full version history. | ||||||
|   | |||||||
| @@ -364,6 +364,48 @@ function errors.table_key_equals(start_pos, end_pos) | |||||||
|     } |     } | ||||||
| end | end | ||||||
|  |  | ||||||
|  | --[[- There is a trailing comma in this list of function arguments. | ||||||
|  |  | ||||||
|  | @tparam number token The token id. | ||||||
|  | @tparam number token_start The start position of the token. | ||||||
|  | @tparam number token_end The end position of the token. | ||||||
|  | @tparam number prev The start position of the previous entry. | ||||||
|  | @treturn table The resulting parse error. | ||||||
|  | ]] | ||||||
|  | function errors.missing_table_comma(token, token_start, token_end, prev) | ||||||
|  |     expect(1, token, "number") | ||||||
|  |     expect(2, token_start, "number") | ||||||
|  |     expect(3, token_end, "number") | ||||||
|  |     expect(4, prev, "number") | ||||||
|  |  | ||||||
|  |     return { | ||||||
|  |         "Unexpected " .. token_names[token] .. " in table.", | ||||||
|  |         annotate(token_start, token_end), | ||||||
|  |         annotate(prev + 1, prev + 1, "Are you missing a comma here?"), | ||||||
|  |     } | ||||||
|  | end | ||||||
|  |  | ||||||
|  | --[[- There is a trailing comma in this list of function arguments. | ||||||
|  |  | ||||||
|  | @tparam number comma_start The start position of the `,` token. | ||||||
|  | @tparam number comma_end The end position of the `,` token. | ||||||
|  | @tparam number paren_start The start position of the `)` token. | ||||||
|  | @tparam number paren_end The end position of the `)` token. | ||||||
|  | @treturn table The resulting parse error. | ||||||
|  | ]] | ||||||
|  | function errors.trailing_call_comma(comma_start, comma_end, paren_start, paren_end) | ||||||
|  |     expect(1, comma_start, "number") | ||||||
|  |     expect(2, comma_end, "number") | ||||||
|  |     expect(3, paren_start, "number") | ||||||
|  |     expect(4, paren_end, "number") | ||||||
|  |  | ||||||
|  |     return { | ||||||
|  |         "Unexpected " .. code(")") .. " in function call.", | ||||||
|  |         annotate(paren_start, paren_end), | ||||||
|  |         annotate(comma_start, comma_end, "Tip: Try removing this " .. code(",") .. "."), | ||||||
|  |     } | ||||||
|  | end | ||||||
|  |  | ||||||
| -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||||||
| -- Statement parsing errors | -- Statement parsing errors | ||||||
| -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||||||
|   | |||||||
| @@ -17,6 +17,8 @@ local error_printer = require "cc.internal.error_printer" | |||||||
| local error_sentinel = {} | local error_sentinel = {} | ||||||
|  |  | ||||||
| local function make_context(input) | local function make_context(input) | ||||||
|  |     expect(1, input, "string") | ||||||
|  |  | ||||||
|     local context = {} |     local context = {} | ||||||
|  |  | ||||||
|     local lines = { 1 } |     local lines = { 1 } | ||||||
| @@ -69,8 +71,9 @@ local function parse(input, start_symbol) | |||||||
|     expect(2, start_symbol, "number") |     expect(2, start_symbol, "number") | ||||||
|  |  | ||||||
|     local context = make_context(input) |     local context = make_context(input) | ||||||
|     function context.report(msg) |     function context.report(msg, ...) | ||||||
|         expect(1, msg, "table") |         expect(1, msg, "table", "function") | ||||||
|  |         if type(msg) == "function" then msg = msg(...) end | ||||||
|         error_printer(context, msg) |         error_printer(context, msg) | ||||||
|         error(error_sentinel) |         error(error_sentinel) | ||||||
|     end |     end | ||||||
| @@ -106,8 +109,9 @@ local function parse_repl(input) | |||||||
|     local context = make_context(input) |     local context = make_context(input) | ||||||
|  |  | ||||||
|     local last_error = nil |     local last_error = nil | ||||||
|     function context.report(msg) |     function context.report(msg, ...) | ||||||
|         expect(1, msg, "table") |         expect(1, msg, "table", "function") | ||||||
|  |         if type(msg) == "function" then msg = msg(...) end | ||||||
|         last_error = msg |         last_error = msg | ||||||
|         error(error_sentinel) |         error(error_sentinel) | ||||||
|     end |     end | ||||||
| @@ -120,22 +124,35 @@ local function parse_repl(input) | |||||||
|         assert(coroutine.resume(parsers[i], context, coroutine.yield, start_code)) |         assert(coroutine.resume(parsers[i], context, coroutine.yield, start_code)) | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  |     -- Run all parsers together in parallel, feeding them one token at a time. | ||||||
|  |     -- Once all parsers have failed, report the last failure (corresponding to | ||||||
|  |     -- the longest parse). | ||||||
|     local ok, err = pcall(function() |     local ok, err = pcall(function() | ||||||
|         local parsers_n = #parsers |         local parsers_n = #parsers | ||||||
|         while true do |         while true do | ||||||
|             local token, start, finish = lexer() |             local token, start, finish = lexer() | ||||||
|  |  | ||||||
|             local stop = true |             local all_failed = true | ||||||
|             for i = 1, parsers_n do |             for i = 1, parsers_n do | ||||||
|                 local parser = parsers[i] |                 local parser = parsers[i] | ||||||
|                 if coroutine.status(parser) ~= "dead" then |                 if parser then | ||||||
|                     stop = false |  | ||||||
|                     local ok, err = coroutine.resume(parser, token, start, finish) |                     local ok, err = coroutine.resume(parser, token, start, finish) | ||||||
|                     if not ok and err ~= error_sentinel then error(err, 0) end |                     if ok then | ||||||
|  |                         -- This parser accepted our input, succeed immediately. | ||||||
|  |                         if coroutine.status(parser) == "dead" then return end | ||||||
|  |  | ||||||
|  |                         all_failed = false -- Otherwise continue parsing. | ||||||
|  |                     elseif err ~= error_sentinel then | ||||||
|  |                         -- An internal error occurred: propagate it. | ||||||
|  |                         error(err, 0) | ||||||
|  |                     else | ||||||
|  |                         -- The parser failed, stub it out so we don't try to continue using it. | ||||||
|  |                         parsers[i] = false | ||||||
|  |                     end | ||||||
|                 end |                 end | ||||||
|             end |             end | ||||||
|  |  | ||||||
|             if stop then error(error_sentinel) end |             if all_failed then error(error_sentinel) end | ||||||
|         end |         end | ||||||
|     end) |     end) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -92,7 +92,7 @@ local function lex_number(context, str, start) | |||||||
|     local contents = sub(str, start, pos - 1) |     local contents = sub(str, start, pos - 1) | ||||||
|     if not tonumber(contents) then |     if not tonumber(contents) then | ||||||
|         -- TODO: Separate error for "2..3"? |         -- TODO: Separate error for "2..3"? | ||||||
|         context.report(errors.malformed_number(start, pos - 1)) |         context.report(errors.malformed_number, start, pos - 1) | ||||||
|     end |     end | ||||||
|  |  | ||||||
|     return tokens.NUMBER, pos - 1 |     return tokens.NUMBER, pos - 1 | ||||||
| @@ -114,14 +114,14 @@ local function lex_string(context, str, start_pos, quote) | |||||||
|             return tokens.STRING, pos |             return tokens.STRING, pos | ||||||
|         elseif c == "\n" or c == "\r" or c == "" then |         elseif c == "\n" or c == "\r" or c == "" then | ||||||
|             -- We don't call newline here, as that's done for the next token. |             -- We don't call newline here, as that's done for the next token. | ||||||
|             context.report(errors.unfinished_string(start_pos, pos, quote)) |             context.report(errors.unfinished_string, start_pos, pos, quote) | ||||||
|             return tokens.STRING, pos - 1 |             return tokens.STRING, pos - 1 | ||||||
|         elseif c == "\\" then |         elseif c == "\\" then | ||||||
|             c = sub(str, pos + 1, pos + 1) |             c = sub(str, pos + 1, pos + 1) | ||||||
|             if c == "\n" or c == "\r" then |             if c == "\n" or c == "\r" then | ||||||
|                 pos = newline(context, str, pos + 1, c) |                 pos = newline(context, str, pos + 1, c) | ||||||
|             elseif c == "" then |             elseif c == "" then | ||||||
|                 context.report(errors.unfinished_string_escape(start_pos, pos, quote)) |                 context.report(errors.unfinished_string_escape, start_pos, pos, quote) | ||||||
|                 return tokens.STRING, pos |                 return tokens.STRING, pos | ||||||
|             elseif c == "z" then |             elseif c == "z" then | ||||||
|                 pos = pos + 2 |                 pos = pos + 2 | ||||||
| @@ -129,7 +129,7 @@ local function lex_string(context, str, start_pos, quote) | |||||||
|                     local next_pos, _, c  = find(str, "([%S\r\n])", pos) |                     local next_pos, _, c  = find(str, "([%S\r\n])", pos) | ||||||
|  |  | ||||||
|                     if not next_pos then |                     if not next_pos then | ||||||
|                         context.report(errors.unfinished_string(start_pos, #str, quote)) |                         context.report(errors.unfinished_string, start_pos, #str, quote) | ||||||
|                         return tokens.STRING, #str |                         return tokens.STRING, #str | ||||||
|                     end |                     end | ||||||
|  |  | ||||||
| @@ -192,7 +192,7 @@ local function lex_long_str(context, str, start, len) | |||||||
|         elseif c == "[" then |         elseif c == "[" then | ||||||
|             local ok, boundary_pos = lex_long_str_boundary(str, pos + 1, "[") |             local ok, boundary_pos = lex_long_str_boundary(str, pos + 1, "[") | ||||||
|             if ok and boundary_pos - pos == len and len == 1 then |             if ok and boundary_pos - pos == len and len == 1 then | ||||||
|                 context.report(errors.nested_long_str(pos, boundary_pos)) |                 context.report(errors.nested_long_str, pos, boundary_pos) | ||||||
|             end |             end | ||||||
|  |  | ||||||
|             pos = boundary_pos |             pos = boundary_pos | ||||||
| @@ -234,12 +234,12 @@ local function lex_token(context, str, pos) | |||||||
|             local end_pos = lex_long_str(context, str, boundary_pos + 1, boundary_pos - pos) |             local end_pos = lex_long_str(context, str, boundary_pos + 1, boundary_pos - pos) | ||||||
|             if end_pos then return tokens.STRING, end_pos end |             if end_pos then return tokens.STRING, end_pos end | ||||||
|  |  | ||||||
|             context.report(errors.unfinished_long_string(pos, boundary_pos, boundary_pos - pos)) |             context.report(errors.unfinished_long_string, pos, boundary_pos, boundary_pos - pos) | ||||||
|             return tokens.ERROR, #str |             return tokens.ERROR, #str | ||||||
|         elseif pos + 1 == boundary_pos then -- Just a "[" |         elseif pos + 1 == boundary_pos then -- Just a "[" | ||||||
|             return tokens.OSQUARE, pos |             return tokens.OSQUARE, pos | ||||||
|         else -- Malformed long string, for instance "[=" |         else -- Malformed long string, for instance "[=" | ||||||
|             context.report(errors.malformed_long_string(pos, boundary_pos, boundary_pos - pos)) |             context.report(errors.malformed_long_string, pos, boundary_pos, boundary_pos - pos) | ||||||
|             return tokens.ERROR, boundary_pos |             return tokens.ERROR, boundary_pos | ||||||
|         end |         end | ||||||
|  |  | ||||||
| @@ -256,7 +256,7 @@ local function lex_token(context, str, pos) | |||||||
|                 local end_pos = lex_long_str(context, str, boundary_pos + 1, boundary_pos - comment_pos) |                 local end_pos = lex_long_str(context, str, boundary_pos + 1, boundary_pos - comment_pos) | ||||||
|                 if end_pos then return tokens.COMMENT, end_pos end |                 if end_pos then return tokens.COMMENT, end_pos end | ||||||
|  |  | ||||||
|                 context.report(errors.unfinished_long_comment(pos, boundary_pos, boundary_pos - comment_pos)) |                 context.report(errors.unfinished_long_comment, pos, boundary_pos, boundary_pos - comment_pos) | ||||||
|                 return tokens.ERROR, #str |                 return tokens.ERROR, #str | ||||||
|             end |             end | ||||||
|         end |         end | ||||||
| @@ -313,18 +313,18 @@ local function lex_token(context, str, pos) | |||||||
|         if end_pos - pos <= 3 then |         if end_pos - pos <= 3 then | ||||||
|             local contents = sub(str, pos, end_pos) |             local contents = sub(str, pos, end_pos) | ||||||
|             if contents == "&&" then |             if contents == "&&" then | ||||||
|                 context.report(errors.wrong_and(pos, end_pos)) |                 context.report(errors.wrong_and, pos, end_pos) | ||||||
|                 return tokens.AND, end_pos |                 return tokens.AND, end_pos | ||||||
|             elseif contents == "||" then |             elseif contents == "||" then | ||||||
|                 context.report(errors.wrong_or(pos, end_pos)) |                 context.report(errors.wrong_or, pos, end_pos) | ||||||
|                 return tokens.OR, end_pos |                 return tokens.OR, end_pos | ||||||
|             elseif contents == "!=" or contents == "<>" then |             elseif contents == "!=" or contents == "<>" then | ||||||
|                 context.report(errors.wrong_ne(pos, end_pos)) |                 context.report(errors.wrong_ne, pos, end_pos) | ||||||
|                 return tokens.NE, end_pos |                 return tokens.NE, end_pos | ||||||
|             end |             end | ||||||
|         end |         end | ||||||
|  |  | ||||||
|         context.report(errors.unexpected_character(pos)) |         context.report(errors.unexpected_character, pos) | ||||||
|         return tokens.ERROR, end_pos |         return tokens.ERROR, end_pos | ||||||
|     end |     end | ||||||
| end | end | ||||||
|   | |||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -107,7 +107,9 @@ local function set_status(text, ok) | |||||||
|     status_text = text |     status_text = text | ||||||
| end | end | ||||||
|  |  | ||||||
| if not bReadOnly and fs.getFreeSpace(sPath) < 1024 then | if bReadOnly then | ||||||
|  |     set_status("File is read only", false) | ||||||
|  | elseif fs.getFreeSpace(sPath) < 1024 then | ||||||
|     set_status("Disk is low on space", false) |     set_status("Disk is low on space", false) | ||||||
| else | else | ||||||
|     local message |     local message | ||||||
|   | |||||||
| @@ -26,6 +26,12 @@ local function pcm_decoder(chunk) | |||||||
|     return buffer |     return buffer | ||||||
| end | end | ||||||
|  |  | ||||||
|  | local function report_invalid_format(format) | ||||||
|  |     printError(("speaker cannot play %s files."):format(format)) | ||||||
|  |     local pp = require "cc.pretty" | ||||||
|  |     pp.print("Run '" .. pp.text("help speaker", colours.lightGrey) .. "' for information on supported formats.") | ||||||
|  | end | ||||||
|  |  | ||||||
|  |  | ||||||
| local cmd = ... | local cmd = ... | ||||||
| if cmd == "stop" then | if cmd == "stop" then | ||||||
| @@ -93,6 +99,11 @@ elseif cmd == "play" then | |||||||
|  |  | ||||||
|         handle.read(4) |         handle.read(4) | ||||||
|         start = nil |         start = nil | ||||||
|  |     -- Detect several other common audio files. | ||||||
|  |     elseif start == "OggS" then return report_invalid_format("Ogg") | ||||||
|  |     elseif start == "fLaC" then return report_invalid_format("FLAC") | ||||||
|  |     elseif start:sub(1, 3) == "ID3" then return report_invalid_format("MP3") | ||||||
|  |     elseif start == "<!DO" --[[<!DOCTYPE]] then return report_invalid_format("HTML") | ||||||
|     end |     end | ||||||
|  |  | ||||||
|     print("Playing " .. file) |     print("Playing " .. file) | ||||||
|   | |||||||
| @@ -21,21 +21,13 @@ local tEnv = { | |||||||
| } | } | ||||||
| setmetatable(tEnv, { __index = _ENV }) | setmetatable(tEnv, { __index = _ENV }) | ||||||
|  |  | ||||||
| -- Replace our package.path, so that it loads from the current directory, rather | -- Replace our require with new instance that loads from the current directory | ||||||
| -- than from /rom/programs. This makes it a little more friendly to use and | -- rather than from /rom/programs. This makes it more friendly to use and closer | ||||||
| -- closer to what you'd expect. | -- to what you'd expect. | ||||||
| do | do | ||||||
|  |     local make_package = require "cc.require".make | ||||||
|     local dir = shell.dir() |     local dir = shell.dir() | ||||||
|     if dir:sub(1, 1) ~= "/" then dir = "/" .. dir end |     _ENV.require, _ENV.package = make_package(_ENV, dir) | ||||||
|     if dir:sub(-1) ~= "/" then dir = dir .. "/" end |  | ||||||
|  |  | ||||||
|     local strip_path = "?;?.lua;?/init.lua;" |  | ||||||
|     local path = package.path |  | ||||||
|     if path:sub(1, #strip_path) == strip_path then |  | ||||||
|         path = path:sub(#strip_path + 1) |  | ||||||
|     end |  | ||||||
|  |  | ||||||
|     package.path = dir .. "?;" .. dir .. "?.lua;" .. dir .. "?/init.lua;" .. path |  | ||||||
| end | end | ||||||
|  |  | ||||||
| if term.isColour() then | if term.isColour() then | ||||||
| @@ -78,18 +70,13 @@ while running do | |||||||
|  |  | ||||||
|     local name, offset = "=lua[" .. chunk_idx .. "]", 0 |     local name, offset = "=lua[" .. chunk_idx .. "]", 0 | ||||||
|  |  | ||||||
|     local force_print = 0 |  | ||||||
|     local func, err = load(input, name, "t", tEnv) |     local func, err = load(input, name, "t", tEnv) | ||||||
|  |     if load("return " .. input) then | ||||||
|     local expr_func = load("return _echo(" .. input .. ");", name, "t", tEnv) |         -- We wrap the expression with a call to _echo(...), which prevents tail | ||||||
|     if not func then |         -- calls (and thus confusing errors). Note we check this is a valid | ||||||
|         if expr_func then |         -- expression separately, to avoid accepting inputs like `)--` (which are | ||||||
|             func = expr_func |         -- parsed as `_echo()--)`. | ||||||
|             offset = 13 |         func = load("return _echo(" .. input .. "\n)", name, "t", tEnv) | ||||||
|             force_print = 1 |  | ||||||
|         end |  | ||||||
|     elseif expr_func then |  | ||||||
|         func = expr_func |  | ||||||
|         offset = 13 |         offset = 13 | ||||||
|     end |     end | ||||||
|  |  | ||||||
| @@ -99,9 +86,8 @@ while running do | |||||||
|  |  | ||||||
|         local results = table.pack(exception.try(func)) |         local results = table.pack(exception.try(func)) | ||||||
|         if results[1] then |         if results[1] then | ||||||
|             local n = 1 |             for i = 2, results.n do | ||||||
|             while n < results.n or n <= force_print do |                 local value = results[i] | ||||||
|                 local value = results[n + 1] |  | ||||||
|                 local ok, serialised = pcall(pretty.pretty, value, { |                 local ok, serialised = pcall(pretty.pretty, value, { | ||||||
|                     function_args = settings.get("lua.function_args"), |                     function_args = settings.get("lua.function_args"), | ||||||
|                     function_source = settings.get("lua.function_source"), |                     function_source = settings.get("lua.function_source"), | ||||||
| @@ -111,7 +97,6 @@ while running do | |||||||
|                 else |                 else | ||||||
|                     print(tostring(value)) |                     print(tostring(value)) | ||||||
|                 end |                 end | ||||||
|                 n = n + 1 |  | ||||||
|             end |             end | ||||||
|         else |         else | ||||||
|             printError(results[2]) |             printError(results[2]) | ||||||
|   | |||||||
| @@ -34,7 +34,14 @@ public class AddressRuleTest | |||||||
|     @ValueSource( strings = { |     @ValueSource( strings = { | ||||||
|         "0.0.0.0", "[::]", |         "0.0.0.0", "[::]", | ||||||
|         "localhost", "127.0.0.1.nip.io", "127.0.0.1", "[::1]", |         "localhost", "127.0.0.1.nip.io", "127.0.0.1", "[::1]", | ||||||
|         "172.17.0.1", "192.168.1.114", "[0:0:0:0:0:ffff:c0a8:172]", "10.0.0.1" |         "172.17.0.1", "192.168.1.114", "[0:0:0:0:0:ffff:c0a8:172]", "10.0.0.1", | ||||||
|  |         // Multicast | ||||||
|  |         "224.0.0.1", "ff02::1", | ||||||
|  |         // Cloud metadata providers | ||||||
|  |         "100.100.100.200", // Alibaba | ||||||
|  |         "192.0.0.192", // Oracle | ||||||
|  |         "fd00:ec2::254", // AWS | ||||||
|  |         "169.254.169.254" // AWS, Digital Ocean, GCP, etc.. | ||||||
|     } ) |     } ) | ||||||
|     public void blocksLocalDomains( String domain ) |     public void blocksLocalDomains( String domain ) | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -49,4 +49,18 @@ describe("cc.internal.syntax", function() | |||||||
|     describe_golden("the lexer", "lexer_spec.md", true) |     describe_golden("the lexer", "lexer_spec.md", true) | ||||||
|     describe_golden("the parser", "parser_spec.md", false) |     describe_golden("the parser", "parser_spec.md", false) | ||||||
|     describe_golden("the parser (all states)", "parser_exhaustive_spec.md", false) |     describe_golden("the parser (all states)", "parser_exhaustive_spec.md", false) | ||||||
|  |  | ||||||
|  |     describe("the REPL input parser", function() | ||||||
|  |         it("returns true when accepted by both parsers", function() | ||||||
|  |             helpers.with_window(50, 10, function() | ||||||
|  |                 expect(syntax.parse_repl("print(x)")):eq(true) | ||||||
|  |             end) | ||||||
|  |         end) | ||||||
|  |  | ||||||
|  |         it("returns true when accepted by one parser", function() | ||||||
|  |             helpers.with_window(50, 10, function() | ||||||
|  |                 expect(syntax.parse_repl("x")):eq(true) | ||||||
|  |             end) | ||||||
|  |         end) | ||||||
|  |     end) | ||||||
| end) | end) | ||||||
|   | |||||||
| @@ -45,8 +45,9 @@ local function capture_parser(input, print_tokens, start) | |||||||
|     end |     end | ||||||
|  |  | ||||||
|     local context = make_context(input) |     local context = make_context(input) | ||||||
|     function context.report(message) |     function context.report(message, ...) | ||||||
|         expect(3, message, "table") |         expect(3, message, "table", "function") | ||||||
|  |         if type(message) == "function" then message = message(...) end | ||||||
|  |  | ||||||
|         for _, msg in ipairs(message) do |         for _, msg in ipairs(message) do | ||||||
|             if type(msg) == "table" and msg.tag == "annotate" then |             if type(msg) == "table" and msg.tag == "annotate" then | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Jonathan Coates
					Jonathan Coates